I valori all'interno delle monadi, annidati in strutture di dati?
-
18-09-2019 - |
Domanda
Supponiamo che in un Haskell programma ho alcuni dati il cui tipo è qualcosa di simile a:
IO [ IO (Int, String, Int) ]
, oIO [ (Int, String, IO Int) ]
, o[ (Int, String, IO Int) ]
ma ho pure funzioni che devono operare in [ (Int, String, Int) ]
.Sembra che devo goffamente rimuovere l'interno valori IO monade, fino a quando ho avuto qualcosa di simile IO [ (Int, string, Int) ] e poi da dentro l'IO monade) applicare le funzioni pure.Non è facile pre-definite modo per fare questo, suppongo?Qualcosa che potrebbe sollevare tutta una struttura di dati in una monade, trasformando tutti all'interno tipi di tipi puri?(Sarebbe molto comodo!)
Soluzione
Si potrebbe utilizzare il liftM*
funzione dall' Di controllo.Monade module, o il liftA*
funzioni per applicatives.
liftM
permette di sollevare una pura funzione di lavoro all'interno di una Monade, ad esempio:
ghci> let s = return "Hello" :: IO String
ghci> liftM reverse s
"olleH"
In questo modo non devi scrivere manualmente cose come "s >>= \x -> return (reverse x)
"ovunque.
Anche se, questo non vi aiuterà con il vostro [(String, Int, IO Int)]
esempio, se la pura funzione si occupa di un [(String, Int, Int)]
.Dal momento che il terzo elemento della tupla non è un' Int
.
In questo caso suggerisco di primo scrivere una funzione [(String, Int, IO Int)] -> IO [(String, Int, Int)]
e che si applicano sollevato pura funzione.
Questa è la più generale funzione mi potesse venire in mente di fare questo:
conv :: Monad m => (f (m a) -> m (f a)) -> [f (m a)] -> m [f a]
conv f = sequence . map f
Si può chiamare in questo modo:
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)]
Questo funziona solo se si dispone di una singola monade che è da qualche parte nel profondo di un tipo.Se si dispone di più, penso che si dovrebbe davvero pensare al tipo di lavoro e vedere se si può rendere più semplice.
Altri suggerimenti
Prima qualche esempio di utilizzo per la soluzione di seguito denominato reduce
(a meno che tu suggerisci un nome migliore):
> 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')]
Il tuo esempio è anche risolto con questo:
complexReduce :: Monad m => m (m (a, b, m [m (c, m d)])) -> m (a, b, [(c, d)])
complexReduce = reduce
E l'attuazione di 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)