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.
Monad transformer. In functional programming, a monad transformer is a type constructor which takes a monad as an argument and returns a monad as a result. Monad transformers can be used to compose features encapsulated by monads – such as state, exception handling, and I/O – in a modular way.
In a monad transformer, the lift function allows you to run actions in the underlying monad. This behavior is encompassed by the MonadTrans class: So using lift in the ReaderT Env IO action allows IO functions. Using the type template from the class, we can substitute Reader Env for t, and IO for m.
Note that monad transformations are usually not commutative: for instance, applying the state transformer to the option monad yields a type (a computation which may fail and yield no final state), whereas the converse transformation has type (a computation which yields a final state and an optional return value).
Thus helper functions are frequently used for this. Additionally, since monad transformers can run several layers deep, the types can get complicated. So it is typical to use type synonyms liberally. As a similar idea, there are some typeclasses which allow you to make certain assumptions about the monad stack below.
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 ...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With