2009-07-21 13 views
5

Supposons que dans un programme Haskell j'ai des données dont le type est quelque chose comme:Valeurs à l'intérieur des monades, imbriquées dans des structures de données?

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

mais j'ai des fonctions pures qui devrait fonctionner sur [ (Int, String, Int) ]. Il semble que je devrais enlever maladroitement les valeurs internes de la monothèque IO, jusqu'à ce que j'obtienne quelque chose comme IO [(Int, string, Int)] puis (depuis l'intérieur de la monade IO) appliquer les fonctions pures. Il n'y a pas de moyen facile et prédéfini de le faire, je suppose? Quelque chose qui soutiendrait toute une structure de données en une monade, transformant tous en types en types purs? (Ce serait très pratique!)

+1

Merci les gars pour les excellentes réponses! Vous avez été absolument utile! – Jay

Répondre

6

Vous pouvez utiliser la fonction liftM* à partir du module Control.Monad, ou les fonctions liftA* pour applicatives.

liftM vous permet de soulever une fonction pure pour travailler à l'intérieur d'une monade, .: par exemple

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

De cette façon, vous ne devez pas écrire manuellement des choses comme « s >>= \x -> return (reverse x) » partout.

Bien que, cela ne vous aidera pas avec votre exemple [(String, Int, IO Int)], si la fonction pure que vous avez traite un [(String, Int, Int)]. Depuis le troisième élément dans le tuple n'est pas vraiment un Int.

Dans ce cas, je suggère d'écrire d'abord une fonction [(String, Int, IO Int)] -> IO [(String, Int, Int)] et qui appliquent la fonction pure levée.


Ceci est la fonction la plus générale que je pourrais trouver pour ce faire:

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

Vous pouvez l'appeler comme ceci:

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

Cette fonction ne fonctionnera que si vous avoir une seule monade qui est quelque part au fond d'un type. Si vous en avez plusieurs, je pense que vous devriez vraiment penser au type dans lequel vous travaillez et voir si vous ne pouvez pas simplifier.

+0

C'est intéressant! Peut-être que la langue devrait avoir quelque chose comme ça intégré?Quelque chose qui fonctionnerait pour tous les types (pensez à une liste * à l'intérieur * d'un tuple, par exemple - ou aux types algébriques de fata ...) – Jay

+0

Par ailleurs ... Utiliser une séquence signifierait que je ne peux pas l'utiliser sur des listes infinies , droite? – Jay

+0

@Jay: Peut-être que quelque chose peut être fait avec 'unsafeInterleaveIO', mais en effet' sequence' sur une liste infinie prend beaucoup de temps. – ephemient

4

d'abord, un exemple d'utilisation de la solution ci-dessous appelée reduce (à moins que vous suggérez un meilleur nom):

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

Votre exemple est également résolu avec elle:

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

Et la mise en œuvre 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)