2009-07-21 11 views
5

Supongamos que en un programa Haskell tengo algunos datos cuyo tipo es algo así como:Valores dentro de mónadas, anidados en estructuras de datos?

  • IO [ IO (Int, String, Int) ], o
  • IO [ (Int, String, IO Int) ] o
  • [ (Int, String, IO Int) ]

pero tengo funciones puras que deben operar en [ (Int, String, Int) ]. Parece que tendría que eliminar torpemente los valores internos de la mónada IO, hasta que obtuve algo como IO [(Int, string, Int)] y luego (desde dentro de la mónada IO) aplicar las funciones puras. No hay una manera predefinida de hacerlo, supongo. ¿Algo que elevaría toda una estructura de datos en una mónada, convirtiendo todos los dentro de los tipos en tipos puros? (Eso sería muy conveniente!)

+1

¡Gracias a todos por las excelentes respuestas! ¡Fuiste absolutamente útil! – Jay

Respuesta

6

usted podría utilizar la función liftM* del módulo Control.Monad, o las funciones para liftA*applicatives.

liftM le permite levantar una función pura a trabajar dentro de una mónada, por ejemplo .:

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

De esta manera usted no tiene que escribir manualmente cosas como "s >>= \x -> return (reverse x)" en todas partes.

Aunque esto no lo ayudará con su ejemplo [(String, Int, IO Int)], si la función pura tiene que ver con [(String, Int, Int)]. Dado que el tercer elemento en la tupla en realidad no es un Int.

En ese caso, sugiero que primero escriba una función [(String, Int, IO Int)] -> IO [(String, Int, Int)] y que aplique la función levantada pura.


Esta es la función más general que podía llegar a hacer esto:

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

se le puede llamar así:

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)] 

Esta función sólo funcionará si tener una mónada única en algún lugar profundo de un tipo. Si tienes varios, creo que realmente deberías estar pensando en el tipo con el que trabajas y ver si no puedes simplificarlo.

+0

¡Eso es interesante! Tal vez el lenguaje debería tener algo así como incorporado?Algo que funcionaría para todos los tipos (piense en una lista * dentro * de una tupla, por ejemplo - o tipos fata algebraicos ...) – Jay

+0

Por cierto ... Usar secuencia significaría que no puedo usarlo en listas infinitas , ¿derecho? – Jay

+0

@Jay: Quizás se pueda hacer algo con 'inseveInterleaveIO', pero de hecho 'sequence' en una lista infinita lleva bastante tiempo. – ephemient

4

Primera algún ejemplo de utilización de la solución por debajo de llamada reduce (a menos que usted sugiere un nombre mejor):

> 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')] 

Su ejemplo también se resuelve con él:

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

y la implementación de 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)