Angenommen, ich habe ein einfaches Erzeuger-/Verbrauchermodell, bei dem der Verbraucher einen bestimmten Status an den Hersteller zurückgeben möchte. Zum Beispiel können die stromabwärts fließenden Objekte Objekte sein, die wir in eine Datei schreiben wollen, und die vorgelagerten Objekte ein Token sein, das darstellt, wo das Objekt in die Datei geschrieben wurde (z. B. ein Offset).Idiomatische bidirektionale Pipes mit Downstream-Status ohne Verlust
Diese beiden Prozesse könnte wie folgt aussehen (mit pipes-4.0
),
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import Pipes
import Pipes.Core
import Control.Monad.Trans.State
import Control.Monad
newtype Object = Obj Int
deriving (Show)
newtype ObjectId = ObjId Int
deriving (Show, Num)
writeObjects :: Proxy ObjectId Object() X IO r
writeObjects = evalStateT (forever go) (ObjId 0)
where go = do i <- get
obj <- lift $ request i
lift $ lift $ putStrLn $ "Wrote "++show obj
modify (+1)
produceObjects :: [Object] -> Proxy X() ObjectId Object IO()
produceObjects = go
where go [] = return()
go (obj:rest) = do
lift $ putStrLn $ "Producing "++show obj
objId <- respond obj
lift $ putStrLn $ "Object "++show obj++" has ID "++show objId
go rest
objects = [ Obj i | i <- [0..10] ]
So einfach das auch sein mag, ich habe ein gutes Stück von Schwierigkeiten hatte Argumentation darüber, wie sie zu komponieren. Im Idealfall würde man eine Push-basierte wie die folgende Ablaufsteuerung wünschen,
writeObjects
beginnt, indem aufrequest
Blockierung stromaufwärts der anfänglichenObjId 0
gesendet hat.produceObjects
das erste Objekt sendet,Obj 0
stromabwriteObjects
das Objekt schreibt und seinen Zustand inkrementiert, und wartet aufrequest
, diesmal stromaufObjId 1
respond
inproduceObjects
kehrt mitObjId 0
produceObjects
Senden geht bei Schritt (2) mit dem zweiten Objekt,Obj 1
Mein erster Versuch war mit Push-basierten Zusammensetzung wie folgt
main = void $ run $ produceObjects objects >>~ const writeObjects
Beachten Sie die Verwendung von const
um die ansonsten inkompatible Typen arbeiten (dies wahrscheinlich ist, wo das Problem liegt). In diesem Fall jedoch finden wir, dass ObjId 0
gegessen wird,
Producing Obj 0
Wrote Obj 0
Object Obj 0 has ID ObjId 1
Producing Obj 1
...
Ein Pull-basierten Ansatz,
main = void $ run $ const (produceObjects objects) +>> writeObjects
leidet ein ähnliches Problem, diesmal fallen Obj 0
.
Wie könnte man diese Stücke in der gewünschten Weise komponieren?