2009-07-21 6 views
5

, dass in einem Haskell Programm, das ich einige Daten habe, dessen Typ ist so etwas wie:Werte innerhalb von Monaden, verschachtelt in Datenstrukturen? Angenommen

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

aber ich habe reine Funktionen, sollte operieren auf [ (Int, String, Int) ]. Es scheint, als müsste ich die inneren Werte ungeschickt aus der IO-Monade entfernen, bis ich etwas wie IO [(Int, String, Int)] und dann (von der IO-Monade) die reinen Funktionen übernommen habe. Es gibt keinen einfachen vordefinierten Weg, denke ich? Etwas, das eine ganze Datenstruktur in eine Monade heben würde und alle innerhalb Typen in reine Typen verwandeln würde? (Das wäre sehr bequem!)

+1

Danke Jungs für die ausgezeichneten Antworten! Du warst absolut hilfreich! – Jay

Antwort

6

Sie konnten die liftM* Funktion aus dem Control.Monad Modul verwenden oder die liftA* Funktionen für applicatives.

liftM können Sie eine reine Funktion heben innerhalb einer Monade zu arbeiten, z.B .:

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

Auf diese Weise schreiben Sie müssen nicht manuell Dinge wie „s >>= \x -> return (reverse x)“ überall.

Obwohl dies nicht hilft Ihnen mit Ihrem [(String, Int, IO Int)] Beispiel, wenn die reine Funktion Sie mit einem [(String, Int, Int)] beschäftigt. Da das dritte Element im Tupel wirklich kein Int ist.

In diesem Fall würde ich vorschlagen, zuerst eine Funktion schreiben [(String, Int, IO Int)] -> IO [(String, Int, Int)] und dass die aufgehobene reine Funktion anwenden.


Dies ist die allgemeine Funktion, die ich mit, dies zu tun einfiel:

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

Sie es wie so nennen kann:

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

Wenn Sie diese Funktion wird nur funktionieren, habe eine einzige Monade, die irgendwo tief in einem Typ ist. Wenn Sie mehrere haben, sollten Sie wirklich über den Typ nachdenken, mit dem Sie zusammenarbeiten, und sehen, ob Sie es nicht einfacher machen können.

+0

Das ist interessant! Vielleicht sollte die Sprache so etwas eingebaut haben?Etwas, das für alle Typen funktionieren würde (denke an eine Liste * innerhalb * eines Tupels zum Beispiel - oder an algebraische Fatatypen ...) – Jay

+0

Übrigens ... Die Verwendung der Sequenz würde bedeuten, dass ich sie nicht auf unendlichen Listen verwenden kann , Recht? – Jay

+0

@Jay: Vielleicht kann etwas mit 'unsafeInterleaveIO' gemacht werden, aber in der Tat dauert' sequence' auf einer unendlichen Liste ziemlich lange. – ephemient

4

Zunächst einige Anwendungsbeispiele für die Lösung unter reduce genannt (es sei denn, Sie einen besseren Namen vorschlagen):

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

Ihr Beispiel auch mit ihm gelöst:

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

und die Umsetzung von 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)