2012-09-26 11 views
11

Angenommen, ich große Liste der Elemente bekommen, während sie mit IO arbeiten:faul Version von MAPM

as <- getLargeList 

Jetzt versuche ich fn :: a -> IO b auf as anzuwenden:

as <- getLargeList 
bs <- mapM fn as 

mapM hat Typ mapM :: Monad m => (a -> m b) -> [a] -> m [b], und das ist, was ich in Bezug auf die Typübereinstimmung brauche. Aber es baut die gesamte Kette im Gedächtnis auf, bis das Ergebnis zurückkommt. Ich suche nach Analog von mapM, die träge arbeiten wird, so dass ich Kopf bs verwenden kann, während Schwanz noch immer baut.

+1

Vielleicht wird dies helfen. http://stackoverflow.com/questions/3270255/is-haskells-mapm-not-lazy –

+0

Anton, ich habe dieses Thema gelesen, aber ich habe die Antwort nicht gefunden: Gibt es eine Alternative von mapM für Lazy-Berechnungen. –

+0

@DmitryBespalov Nicht mit der gleichen Signatur. 'Monad' hat keine Abstraktion, um die Effekte auf später zu verschieben - und das ist es, was Sie tun müssen, damit' mapM' fauler wird. – Carl

Antwort

18

Verwenden Sie nicht unsafeInterleaveIO oder irgendein lazy IO für diese Angelegenheit. Dies ist genau das Problem, dass iteratees erstellt wurden, um Folgendes zu beheben: Vermeidung von Lazy IO, was zu unvorhersehbarem Ressourcenmanagement führt. Der Trick besteht darin, niemals die Liste zu erstellen und es kontinuierlich mit Iteraten zu streamen, bis Sie fertig sind. Ich werde Beispiele aus meiner eigenen Bibliothek verwenden, pipes, um dies zu demonstrieren.

Zunächst definieren:

import Control.Monad 
import Control.Monad.Trans 
import Control.Pipe 

-- Demand only 'n' elements 
take' :: (Monad m) => Int -> Pipe a a m() 
take' n = replicateM_ n $ do 
    a <- await 
    yield a 

-- Print all incoming elements 
printer :: (Show a) => Consumer a IO r 
printer = forever $ do 
    a <- await 
    lift $ print a 

Nun wollen wir unseren Benutzer gemein sein und fordern sie die wirklich große Liste für uns produzieren:

prompt100 :: Producer Int IO() 
prompt100 = replicateM_ 1000 $ do 
    lift $ putStrLn "Enter an integer: " 
    n <- lift readLn 
    yield n 

Jetzt wollen wir es laufen:

>>> runPipe $ printer <+< take' 1 <+< prompt100 
Enter an integer: 
3<Enter> 
3 

Es fordert nur den Benutzer für eine ganze Zahl, da wir nur eine ganze Zahl fordern!

Wenn Sie prompt100 mit Ausgabe von getLargeList ersetzen wollen, schreiben Sie einfach:

yourProducer :: Producer b IO() 
yourProducer = do 
    xs <- lift getLargeList 
    mapM_ yield xs 

... und dann laufen:

>>> runPipe $ printer <+< take' 1 <+< yourProducer 

Dies wird lazily die Liste streamen und nie bauen die Liste im Speicher, alles ohne unsichere IO Hacks. Um die Anzahl der von Ihnen angeforderten Elemente zu ändern, ändern Sie einfach den übergebenen Wert in take'

Weitere Beispiele finden Sie unter pipes tutorial unter Control.Pipe.Tutorial.

Um mehr darüber zu erfahren, warum Lazy IO Probleme verursacht, lesen Sie Olegs Original-Folien zum Thema, die Sie finden können here.Er erklärt die Probleme bei der Verwendung von Lazy IO sehr gut. Jedes Mal, wenn Sie gezwungen sind, faule IO zu verwenden, ist eine iteratee-Bibliothek das, was Sie wirklich wollen.

+5

+100 für die Rohre Bibliothek –

+2

Gabriel, danke für die Antwort! Und es ist wirklich nützliche Bibliothek, danke! –

+0

@DmitryBespalov Gern geschehen! Ich bin immer gerne bereit zu helfen. –

6

Die IO-Monade hat einen Mechanismus, Effekte zu verzögern. Es heißt unsafeInterleaveIO. Sie können es verwenden, um den gewünschten Effekt zu erhalten:

import System.IO.Unsafe 

lazyMapM :: (a -> IO b) -> [a] -> IO [b] 
lazyMapM f [] = return [] 
lazyMapM f (x:xs) = do y <- f x 
         ys <- unsafeInterleaveIO $ lazyMapM f xs 
         return (y:ys) 

So wird Lazy IO implementiert. Es ist unsicher, dass die Reihenfolge, in der die Effekte tatsächlich ausgeführt werden, schwer vorherzusagen ist und durch die Reihenfolge bestimmt wird, in der die Elemente der Ergebnisliste ausgewertet werden. Aus diesem Grund ist es wichtig, dass alle IO-Effekte in f gutartig sind, in dem Sinne, dass sie anordnungsunabhängig sein sollten. Ein gutes Beispiel für einen normalerweise ausreichend gutartigen Effekt ist das Lesen aus einer schreibgeschützten Datei.

+1

Aus meiner Sicht ist die Verwendung von System.IO.Unsafe in Haskell so etwas wie ein Hack. Ich bevorzuge es wegen seines "unsicheren" Verhaltens zu vermeiden. Ich freue mich jedoch über Ihre Antwort. –