[Haskell-cafe] addCatch in conduit
Michael Snoyman
michael at snoyman.com
Thu Nov 6 06:27:07 UTC 2014
On Wed, Nov 5, 2014 at 6:44 PM, Yitzchak Gale <gale at sefer.org> wrote:
> In getting the dtd library to compile with recent versions
> of conduit (yes, I know that it's deprecated and Michael
> longer supports it, but we still need it), we came across
> the following bit of code:
>
> -- (snip) --
> CI.ConduitM $ addCatch $ CI.unConduitM src0
> where
> -- (snip) --
> addCatch :: (MonadThrow m, MonadBaseControl IO m)
> => CI.Pipe l i o u m r
> -> CI.Pipe l i o u m r
> addCatch (CI.HaveOutput src close x) = CI.HaveOutput (addCatch
> src) (addCatch' close) x
> addCatch (CI.NeedInput p c) = CI.NeedInput (addCatch . p) (addCatch .
> c)
> addCatch (CI.Done r) = CI.Done r
> addCatch (CI.PipeM msrc) = CI.PipeM (addCatch' $ liftM addCatch msrc)
> addCatch (CI.Leftover p i) = CI.Leftover (addCatch p) i
>
> addCatch' m = m `Lifted.catch` throw rr
>
> We adapted it to the new ConduitM type simply by changing
> the first line to:
>
> CI.ConduitM $ addCatch . CI.unConduitM src0
>
> Not bad, a diff of exactly one character. It compiles and seems
> to work. Does this sound reasonable?
>
> Obviously, we would love to get rid of this use of conduit internals.
> addCatch seems like a general operation, not specific to this
> library. Is there a way to do this in modern conduit without dipping
> into internals? If not - can we propose to add it?
>
> Thanks,
> Yitz
>
That won't work due to how the CPS/codensity transform works. You'll end up
applying the exception catcher to the *entire* pipeline, not just the part
that's currently delimited. To give a more easily understood example, it's
best to look at difference lists (which I think are always a good way to
understand CPS better). I've put together an example here:
https://www.fpcomplete.com/user/snoyberg/random-code-snippets/cps-transform-example
badMap ends up lower casing the entire list. If you stare at it long
enough, the reason becomes obvious: we're keeping our current portion of
the list as a function, applying that function to the rest of the list, and
*then* applying our map. Instead, goodMap needs to apply the current
portion of the list to the empty list to get a concrete list that can be
traversed, traverse it, and then convert it back to a difference list.
However, this is a little bit inefficient, since we'll first traverse the
list once to apply the mapped function, and then traverse it a second time
to go back to the CPS version. Instead, we can combine the two into a
single step, leading to more efficient (but less readable) code in
efficientMap.
All that said: the functionality you need there is now provided by conduit
out of the box via its `MonadCatch` (from the exceptions package) instance.
It may be useful to look at its implementation:
https://github.com/snoyberg/conduit/blob/dbb49aa2a69e00a8817ec98ffc99f0523a84d0eb/conduit/Data/Conduit/Internal/Conduit.hs#L159
The catchC function is similar, but uses MonadBaseControl instead of
MonadCatch:
https://github.com/snoyberg/conduit/blob/dbb49aa2a69e00a8817ec98ffc99f0523a84d0eb/conduit/Data/Conduit/Internal/Conduit.hs#L427
Full code of my snippet for the lazy:
import Data.Char (toLower)
import Data.Monoid
newtype DList a = DList { unDList :: [a] -> [a] }
instance Monoid (DList a) where
mempty = DList id
mappend (DList x) (DList y) = DList (x . y)
fromList :: [a] -> DList a
fromList xs = DList (xs ++)
toList :: DList a -> [a]
toList (DList x) = x []
badMap :: (a -> a) -> DList a -> DList a
badMap f d = DList $ map f . unDList d
goodMap :: (a -> b) -> DList a -> DList b
goodMap f = fromList . map f . toList
efficientMap :: (a -> b) -> DList a -> DList b
efficientMap f =
DList . go . toList
where
go [] = id
go (x:xs) = (f x:) . go xs
main :: IO ()
main = do
putStrLn $ toList $ badMap toLower (fromList "HELLO") <> fromList
"WORLD"
putStrLn $ toList $ goodMap toLower (fromList "HELLO") <> fromList
"WORLD"
putStrLn $ toList $ efficientMap toLower (fromList "HELLO") <> fromList
"WORLD"
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.haskell.org/pipermail/haskell-cafe/attachments/20141106/4625d970/attachment.html>
More information about the Haskell-Cafe
mailing list