Suppose I want to model, using Haskell pipes, a Python
Generator[int, None, None]which keeps some internal state. Should I be usingProducer int (State s) ()orStateT s (Producer int m) (), wheremis whatever type of effect I eventually want from the consumer?How should I think about the notion of transducers in pipes? So in Oleg's simple generators, there is
type Transducer m1 m2 e1 e2 = Producer m1 e1 -> Producer m2 e2but I don't know what the analog is in pipes, because any
Proxyobjects that interact seem to rely on the same underlying monadm, not switching fromm1tom2. See the Prelude functions, for instance.
I think I'm just misunderstanding something fundamental about the way pipes works. Thanks for your help.
In
pipes, you typically wouldn't use effects in the base monadmof your overallEffectto model the internal state of aProducer. If you really wanted to useStatefor this purpose, it would be an internal implementation detail of theProducerin question (discharged by arunStatePorevalStatePinside theProducer, as explained below), and theStatewould not appear in theProducer's type.It's also important to emphasize that a
Producer, even when it's operating in theIdentitybase monad without any "effects" at its disposal, isn't some sort of pure function that would keep producing the same value over and over without monadic help. AProduceris basically a stream, and it can maintain state using the usual functional mechanisms (e.g., recursion, for one). So, you definitely don't need aStatefor aProducerto be stateful.The upshot is that the usual model of a Python
Generator[int, None, None]inPipesis just aMonad m => Producer Int m ()polymorphic in an unspecified base monadm. Only if theProducerneeds some external effects (e.g.,IOto access the filesystem) would you require more ofm(e.g., aMonadIO mconstraint or something).To give you a concrete example, a
Producerthat generates pseudorandom numbers obviously has "state", but a typical implementation would be a "pure"Producer:with the state maintained via recursion.
If you really decided to maintain this state via the
Statemonad, the type of theProducerwouldn't change. You'd just use aStateinternally. ThePipes.Liftmodule provides some helpers (likeevalStatePused here) to locally add a monad layer to facilitate this:Oleg's simple generators are entirely different. His producers and consumers produce and consume values only through monadic effects, and "monad changing" is central to the implementation. In particular, I believe his consumers and transducers can only maintain state via a monadic effect, like a
Statemonad, though I'd have to look a little more carefully to be sure.In contrast,
pipesproxies can produce and consume values and maintain internal state independent of the underlying base monad.Ultimately, the analog of Oleg's transducers in
pipesare simply thePipes. Both consume values from a producer and yield values to a consumer. The monad changing in Oleg's transducers is just an implementation detail.