Niklas Hambüchen | 22 Aug 22:19 2012

Conduit: Where to run monad stacks?

Today I was surprised that transPipe is called for every chunk of data
going through my pipe, rendering the StateT I put in useless, because it
was always restarted with the initial value.

It would be nice to have some explanation about this, as it makes it
easy to write compiling code that has completely unexpected behaviour.

I wrote this function (also on http://hpaste.org/73538):

conduitWithState :: (MonadIO m) => Conduit Int (StateT Int m) String
conduitWithState = do
  liftIO $ putStrLn $ "Counting Int->String converter ready!"
  awaitForever $ \x -> do
    i <- lift get
    lift $ modify (+1)
    liftIO $ putStrLn $ "Converting " ++ show x ++ " to a string! " ++
                        "Processed so far: " ++ show i
    yield (show x)

and ran it like this:

countingConverterConduit :: (MonadIO m) => Conduit Int m String
countingConverterConduit = transPipe (\stateTint -> evalStateT stateTint
1) conduitWithState

main :: IO ()
main = do
  stringList <- CL.sourceList [4,1,9,7,3] $=
     countingConverterConduit $$
     CL.consume
(Continue reading)

Felipe Almeida Lessa | 22 Aug 22:59 2012
Picon

Re: Conduit: Where to run monad stacks?

I can't give you a definite answer.  However, I guess that's because
the monad sequencing (i.e. where >>= of your monad is called) is
inside the 'pipe' function [1] (i.e., $$, $=, =$), the function that
connects pipes and runs them.  'pipe' needs to be able to interleave
lifted actions between both upstream and downstream pipes, and for
that they need to live on the same monad.  What you want is somehow
having upstream and downstream pipes on different monads.

HTH,

[1] http://hackage.haskell.org/packages/archive/conduit/0.5.2.3/doc/html/src/Data-Conduit-Internal.html#pipe

--

-- 
Felipe.
Michael Snoyman | 24 Aug 07:51 2012

Re: Conduit: Where to run monad stacks?

I agree that the behavior is a bit confusing (Dan Burton just filed an
issue about this[1], I'm guessing this email is related).

I put up a wiki page[2] to hopefully explain the issue. Can you review
it and let me know if it helps? If so, I'll link to it from the
Haddocks.

Michael

[1] https://github.com/snoyberg/conduit/issues/67
[2] https://github.com/snoyberg/conduit/wiki/Dealing-with-monad-transformers

On Wed, Aug 22, 2012 at 11:19 PM, Niklas Hambüchen <mail <at> nh2.me> wrote:
> Today I was surprised that transPipe is called for every chunk of data
> going through my pipe, rendering the StateT I put in useless, because it
> was always restarted with the initial value.
>
> It would be nice to have some explanation about this, as it makes it
> easy to write compiling code that has completely unexpected behaviour.
>
>
> I wrote this function (also on http://hpaste.org/73538):
>
> conduitWithState :: (MonadIO m) => Conduit Int (StateT Int m) String
> conduitWithState = do
>   liftIO $ putStrLn $ "Counting Int->String converter ready!"
>   awaitForever $ \x -> do
>     i <- lift get
>     lift $ modify (+1)
>     liftIO $ putStrLn $ "Converting " ++ show x ++ " to a string! " ++
(Continue reading)

Niklas Hambüchen | 24 Aug 16:03 2012

Re: Conduit: Where to run monad stacks?

Hello Michael,

yes, that does certainly help, and it should definitely be linked to.

The remaining question is:

Is it possible to have something like transPipe that runs only once for
the beginning of the pipe?

It seems desirable for me to have conduits which encapsulate monads.
Imagine you have to conduits dealing with stateful encryption/decryption
and one data-counting one in the middle, like:

    decryptConduit $= countConduit $= encryptConduit

Would you really want to combine the three different internal monads
into one single monad of the whole pipe, even though the internal monads
are implementation details and not necessary for the operation of the
whole pipe?

The idea with a Ref inside a Reader sounds like a workaround, but has
the same problem of globalizing/combining effects, somewhat limiting
composability of conduits.

Niklas

On 24/08/12 06:51, Michael Snoyman wrote:
> I agree that the behavior is a bit confusing (Dan Burton just filed an
> issue about this[1], I'm guessing this email is related).
> 
(Continue reading)

Michael Snoyman | 28 Aug 16:46 2012

Re: Conduit: Where to run monad stacks?

On Fri, Aug 24, 2012 at 5:03 PM, Niklas Hambüchen <mail <at> nh2.me> wrote:
> Hello Michael,
>
> yes, that does certainly help, and it should definitely be linked to.
>
> The remaining question is:
>
> Is it possible to have something like transPipe that runs only once for
> the beginning of the pipe?
>
> It seems desirable for me to have conduits which encapsulate monads.
> Imagine you have to conduits dealing with stateful encryption/decryption
> and one data-counting one in the middle, like:
>
>     decryptConduit $= countConduit $= encryptConduit
>
> Would you really want to combine the three different internal monads
> into one single monad of the whole pipe, even though the internal monads
> are implementation details and not necessary for the operation of the
> whole pipe?

I don't disagree with your analysis, but I don't think it's generally
possible to implement the desired transPipe. (If someone can prove
otherwise, I'd be very happy.) It *might* be possible via some (ab)use
of `monad-control` and mutable variables, however.

> The idea with a Ref inside a Reader sounds like a workaround, but has
> the same problem of globalizing/combining effects, somewhat limiting
> composability of conduits.

(Continue reading)


Gmane