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) ()
, wherem
is 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 e2
but I don't know what the analog is in pipes, because any
Proxy
objects that interact seem to rely on the same underlying monadm
, not switching fromm1
tom2
. 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 monadm
of your overallEffect
to model the internal state of aProducer
. If you really wanted to useState
for this purpose, it would be an internal implementation detail of theProducer
in question (discharged by arunStateP
orevalStateP
inside theProducer
, as explained below), and theState
would not appear in theProducer
's type.It's also important to emphasize that a
Producer
, even when it's operating in theIdentity
base 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. AProducer
is basically a stream, and it can maintain state using the usual functional mechanisms (e.g., recursion, for one). So, you definitely don't need aState
for aProducer
to be stateful.The upshot is that the usual model of a Python
Generator[int, None, None]
inPipes
is just aMonad m => Producer Int m ()
polymorphic in an unspecified base monadm
. Only if theProducer
needs some external effects (e.g.,IO
to access the filesystem) would you require more ofm
(e.g., aMonadIO m
constraint or something).To give you a concrete example, a
Producer
that 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
State
monad, the type of theProducer
wouldn't change. You'd just use aState
internally. ThePipes.Lift
module provides some helpers (likeevalStateP
used 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
State
monad, though I'd have to look a little more carefully to be sure.In contrast,
pipes
proxies can produce and consume values and maintain internal state independent of the underlying base monad.Ultimately, the analog of Oleg's transducers in
pipes
are simply thePipe
s. Both consume values from a producer and yield values to a consumer. The monad changing in Oleg's transducers is just an implementation detail.