2009-07-21 4 views
5

Предположим, что в программе Haskell У меня есть некоторые данные, тип которых что-то вроде:Ценности внутри монадов, вложенных в структуры данных?

  • IO [ IO (Int, String, Int) ] или
  • IO [ (Int, String, IO Int) ] или
  • [ (Int, String, IO Int) ]

, но у меня есть чистые функции, которые должны работать на [ (Int, String, Int) ]. Кажется, мне пришлось бы неуклюже удалить внутренние значения из монады IO, пока я не получу что-то вроде IO [(Int, string, Int)], а затем (изнутри монады IO) применяют чистые функции. Полагаю, нет простого заранее определенного способа сделать это? Что-то, что поднимет всю структуру данных в монаду, превратив все в типы в чистые типы? (Это было бы очень удобно!)

+1

Спасибо, ребята, за отличные ответы! Вы были абсолютно полезны! – Jay

ответ

6

Вы можете использовать функцию liftM* из модуля Control.Monad, или liftA* функций для applicatives.

liftM позволяет поднять чистую функцию для работы внутри монады и т.д .:

ghci> let s = return "Hello" :: IO String 
ghci> liftM reverse s 
"olleH" 

Таким образом, вы не должны вручную писать такие вещи, как «s >>= \x -> return (reverse x)» во всем мире.

Хотя, это не поможет вам с вашим примером [(String, Int, IO Int)], если у вас есть чистая функция с [(String, Int, Int)]. Поскольку третий элемент в кортеже действительно не является Int.

В этом случае я бы предложил сначала написать функцию [(String, Int, IO Int)] -> IO [(String, Int, Int)] и применить применимую отмененную функцию.


Это наиболее общая функция, которую я мог придумать, чтобы сделать это:

conv :: Monad m => (f (m a) -> m (f a)) -> [f (m a)] -> m [f a] 
conv f = sequence . map f 

Вы можете назвать это так:

liftTrd :: Monad m => (a, b, m c) -> m (a, b, c) 
liftTrd (x, y, mz) = mz >>= \z -> return (x, y, z) 

conv liftTrd [("hi", 4, return 2)] :: IO [(String, Int, Int)] 

Эта функция будет работать только если вы есть одна монада, где-то глубоко в типе. Если у вас несколько, я думаю, вы действительно должны думать о типе, в котором работаете, и посмотреть, не упростите ли вы его.

+0

Это интересно! Возможно, язык должен иметь что-то подобное этому встроенному?Что-то, что будет работать для всех типов (подумайте о списке * внутри * кортежа, например, или алгебраических типах фата ...) – Jay

+0

Кстати ... Использование последовательности означало бы, что я не могу использовать ее в бесконечных списках , правильно? – Jay

+0

@Jay: Возможно, что-то можно сделать с помощью 'unsafeInterleaveIO', но ведь' sequence' в бесконечном списке занимает довольно много времени. – ephemient

4

Сначала несколько примеров использования для решения ниже называется reduce (если вы не предложить лучшее название):

> reduce [(["ab", "c"], "12")] :: [(String, String)] 
[("ab","12"),("c","12")] 

> reduce [(["ab", "c"], "12")] :: [(Char, Char)] 
[('a','1'),('a','2'),('b','1'),('b','2'),('c','1'),('c','2')] 

> reduce [("ab", "12"), ("cd", "3")] :: [(Char, Char)] 
[('a','1'),('a','2'),('b','1'),('b','2'),('c','3'),('d','3')] 

Ваш пример также решается с ним:

complexReduce :: Monad m => m (m (a, b, m [m (c, m d)])) -> m (a, b, [(c, d)]) 
complexReduce = reduce 

и осуществление reduce :

{-# LANGUAGE FlexibleContexts, FlexibleInstances, IncoherentInstances, MultiParamTypeClasses, UndecidableInstances #-} 

import Control.Monad 

-- reduce reduces types to simpler types, 
-- when the reduction is in one of the following forms: 
-- * make a Monad disappear, like join 
-- * move a Monad out, like sequence 
-- the whole magic of Reduce is all in its instances 
class Reduce s d where 
    reduce :: s -> d 

-- Box is used only for DRY in Reduce instance definitions. 
-- Without it we, a Reduce instance would need 
-- to be tripled for each variable: 
-- Once for a pure value, once for a monadic value, 
-- and once for a reducable value 
newtype Box a = Box { runBox :: a } 
instance Monad m => Reduce (Box a) (m a) where 
    reduce = return . runBox 
instance Reduce a b => Reduce (Box a) b where 
    reduce = reduce . runBox 
redBox :: Reduce (Box a) b => a -> b 
redBox = reduce . Box 

-- we can join 
instance (Monad m 
    , Reduce (Box a) (m b) 
) => Reduce (m a) (m b) where 
    reduce = join . liftM redBox 

-- we can sequence 
-- * instance isnt "Reduce [a] (m [b])" so type is always reduced, 
-- and thus we avoid overlapping instances. 
-- * we cant make it general for any Traversable because then 
-- the type system wont find the right patterns. 
instance (Monad m 
    , Reduce (Box a) (m b) 
) => Reduce (m [a]) (m [b]) where 
    reduce = join . liftM (sequence . fmap redBox) 

instance (Monad m 
    , Reduce (Box a) (m c) 
    , Reduce (Box b) (m d) 
) => Reduce (a, b) (m (c, d)) where 
    reduce (a, b) = liftM2 (,) (redBox a) (redBox b) 

instance (Monad m 
    , Reduce (Box a) (m d) 
    , Reduce (Box b) (m e) 
    , Reduce (Box c) (m f) 
) => Reduce (a, b, c) (m (d, e, f)) where 
    reduce (a, b, c) = 
    liftM3 (,,) (redBox a) (redBox b) (redBox c)