Valores dentro de las mónadas, anidan en las estructuras de datos?
-
18-09-2019 - |
Pregunta
Supongamos que en un programa Haskell tengo algunos datos cuyo tipo es algo como:
-
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 quitar con torpeza los valores dentro de la mónada IO, hasta que llegué algo así como IO [(int, String, int)] y luego (desde el interior de la mónada IO) se aplican las funciones puras. No hay manera predefinida fácil de hacer esto, ¿verdad? Algo que podría levantar una estructura de datos entera en una mónada, convirtiendo todos los dentro de tipos en tipos puros? (Eso sería muy conveniente!)
Solución
Se puede usar la función de la liftM*
de Control módulo .Monad , o las funciones liftA*
para aplicativos .
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.
A pesar de que esto no le ayudará con su ejemplo [(String, Int, IO Int)]
, si la función pura que tiene acuerdos con un [(String, Int, Int)]
. Dado que el tercer elemento de la tupla en realidad no es un Int
.
En ese caso, me gustaría sugerir a escribir primero un [(String, Int, IO Int)] -> IO [(String, Int, Int)]
función y que se aplican a la función pura levantado.
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 tiene una sola mónada que está en algún lugar profundo en un tipo. Si usted tiene múltiples, creo que realmente debería estar pensando en el tipo de su trabajo con y ver si no puede hacerlo más sencillo.
Otros consejos
En primer lugar algunos ejemplos de uso 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 ejecució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)