<div dir="ltr">This got me curious, so I just added a `sourceToList` function to master:<div><br></div><div><a href="https://github.com/snoyberg/conduit/commit/289f671cb7669c4aec78d8e77f01f2ace165d73a">https://github.com/snoyberg/conduit/commit/289f671cb7669c4aec78d8e77f01f2ace165d73a</a><br></div></div><div class="gmail_extra"><br><div class="gmail_quote">On Wed, Nov 4, 2015 at 4:43 AM, Michael Snoyman <span dir="ltr"><<a href="mailto:michael@snoyman.com" target="_blank">michael@snoyman.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr">conduit isn't designed to be used in this way, though in theory such a lazy function would be possible. To get the same effect, you can (ab)use the Data.Conduit.Lazy module, which provides a lazy I/O escape hatch. In this case:<div><br></div><div>> lazyConsume source123Error >>= print</div></div><div class="HOEnZb"><div class="h5"><div class="gmail_extra"><br><div class="gmail_quote">On Wed, Nov 4, 2015 at 12:59 AM, Jules Bean <span dir="ltr"><<a href="mailto:jules@jellybean.co.uk" target="_blank">jules@jellybean.co.uk</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div><div>Conduits seem like a popular and useful way to compose operations<br>
which consume and produce streams at various speeds. I hadn't used<br>
them before, so apologies for any obvious mistakes below.<br>
<br>
Conduits have lots of power in terms of how you can interleave monadic<br>
effects with the streaming; but I found that I had a stream with no<br>
effects at all and I wanted to convert it back into haskells more<br>
simplistic representation, lazy lists.<br>
<br>
This is probably a well-known trick, but I couldn't find how to do this<br>
from googling, so here is the solution I found in case it helps<br>
someone else:<br>
<br>
> {-# LANGUAGE DeriveDataTypeable, FlexibleContexts #-}<br>
> module Main where<br>
><br>
> import Data.Conduit<br>
> import Control.Monad.Catch<br>
> import Control.Monad.Trans.Resource<br>
> import Control.Monad.Writer<br>
> import Control.Monad.Identity<br>
> import qualified Data.Conduit.List as CL<br>
> import Data.Typeable<br>
><br>
> source123Error :: Monad m => Source m Int<br>
> source123Error = do<br>
> yield 1<br>
> yield 2<br>
> yield 3<br>
> error "error"<br>
<br>
A source which produces some data and then hangs, to test lazy production.<br>
<br>
One solution which doesn't work is CL.consume (as its own docs say) - and instead this happens:<br>
<br>
*Main> :t runIdentity (source123Error $$ CL.consume)<br>
runIdentity (source123Error $$ CL.consume) :: [Int]<br>
*Main> runIdentity (source123Error $$ CL.consume)<br>
*** Exception: error<br>
<br>
But what we can do instead is push the data out via the Writer monad:<br>
<br>
> tellEverything :: MonadWriter [a] m => Sink a m ()<br>
> tellEverything = awaitForever (\x -> tell [x])<br>
<br>
*Main> :t source123Error $$ tellEverything<br>
source123Error $$ tellEverything :: MonadWriter [Int] m => m ()<br>
*Main> snd $ runWriter (source123Error $$ tellEverything)<br>
[1,2,3*** Exception: error<br>
<br>
Success! A lazily produced list.<br>
<br>
If your output is of any size - and depending on the compositions<br>
pattern in your code - it may be much faster to use this version:<br>
<br>
> tellEveryEndo :: MonadWriter (Endo [a]) m => Sink a m ()<br>
> tellEveryEndo = awaitForever (\x -> tell (Endo (x:)))<br>
<br>
*Main> ($[]) . appEndo . snd . runWriter $ (source123Error $$ tellOne)<br>
[1,2,3*** Exception: error<br>
<br>
I found a further case where the Conduit I was trying to run was pure<br>
but had a 'MonadThrow' constraint. You can use the same approach here,<br>
using the ExceptionT transformer to satisfy the MonadThrow constraint.<br>
<br>
> data MyError = MyError deriving (Show,Typeable)<br>
> instance Exception MyError<br>
><br>
> source123Throw :: MonadThrow m => Source m Int<br>
> source123Throw = do<br>
> yield 1<br>
> yield 2<br>
> yield 3<br>
> throwM MyError<br>
<br>
*Main Control.Monad.Except Control.Monad.Trans.Resource> snd . runWriter . runExceptionT $ (source123Error $$ tellEverything)<br>
[1,2,3*** Exception: error<br>
*Main Control.Monad.Except Control.Monad.Trans.Resource> snd . runWriter . runExceptionT $ (source123Throw $$ tellEverything)<br>
[1,2,3]<br>
<br>
An aside: I tried to measure the speed difference between the<br>
list-mappend and Endo-mappend approaches with the following<br>
code. count2N uses a binary-tree shaped recursion so it should be<br>
fairly bad for list-mappend with lots of long lists on the left.<br>
<br>
> count2N :: Monad m => Int -> Source m Int<br>
> count2N 0 = yield 0<br>
> count2N n = count2N (n-1) >> count2N (n-1)<br>
<br>
> speedTest1 = print . length . snd . runWriter $ (count2N 24 $$ tellEverything)<br>
> speedTest2 = print . length . ($[]) . appEndo . snd . runWriter $ (count2N 24 $$ tellEveryEndo)<br>
<br>
With -O or -O2 I measure no speed difference between these, they both<br>
take a bit over 2 seconds. With no optimisation flag, speedTest2 is<br>
slower, 17 seconds vs 11 seconds.<br>
<br>
I'm quite surprised the Endo version isn't faster, it seems like<br>
something is rewriting those list appends?<br>
<br>
Cheers,<br>
<br>
Jules<br>
<br>
_______________________________________________<br>
Haskell-Cafe mailing list<br>
<a href="mailto:Haskell-Cafe@haskell.org" target="_blank">Haskell-Cafe@haskell.org</a><br>
<a href="http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe" rel="noreferrer" target="_blank">http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe</a><br>
</div></div></blockquote></div><br></div>
</div></div></blockquote></div><br></div>