7
votes

I have a monad for a computation that may fail and does some logging:

f1 :: WriterT [String] (Either String) a

I have a function that will not fail but does some logging:

f2 :: Writer [String] b

What's the best way to update the writer monad in f1 using the log from f2, and capture the output of the f2 computation? At the moment I'm doing this:

f2result <- (\(r,l) -> do {tell l; return r}) (runWriter f2)

I am using lift to update the inner monad with a different computation, so switching around the Writer and Either monads will not solve the problem.

2
Thanks danr and rampion for your responses. I've chosen to use the wrap method. Although I could change the type of f2, that function appears in other contexts, therefore I'd like to write its type without reference to the needs of a particular calling function.mskel

2 Answers

4
votes

If you defined f2, the easiest possible approach may be to refactor f2 so it's defined thusly:

 f2 :: Monad m => WriterT [String] m b

Which shouldn't be too hard, since Writer w b is defined as WriterT w Identity b, and the Identity monad doesn't give you anything.

Then you'd be able to chain them just by doing f1 >> f2.

If you can't redefine f2, you could always define your own with the appropriate signature:

 f2' :: Monad m => WriterT [String] m b
 f2' = WriterT . return $ runWriter f2

And if you've a bunch of f2 to wrap, you could always define a function to wrap them for you

 wrap :: Monad m => Writer w b -> WriterT w m b
 wrap = WriterT . return . runWriter

So you can do f1 >> wrap f2a >> wrap f2b >> wrap f2c ...

4
votes

As a follow up on rampion's answer, you can instead refactor f2 on any MonadWriter:

f2 :: MonadWriter [String] m => m a

Should in not be possible to change its definition you can wrap it similarly as rampion does:

f2' :: MonadWriter [String] m => m a
f2' = do let (a,w) = runWriter f2
         tell w
         return a

The [String] argument to MonadWriter requires this GHC pragma:

{-# LANGUAGE FlexibleContexts #-}

As always, pragmas are put on the top of the module.

In the comments, rampion gave a version of wrapping a function in this setting:

wrap :: MonadWriter w m => Writer w b -> m b
wrap = uncurry (<<) . (return *** tell) . runWriter 
  where (<<) = flip (>>)