Ilya Smagin
Ilya Smagin

Reputation: 6152

How to call partially un-nest transformers?

Given a nesting of transformers :: T2 of T1 of M0, how can I utilize :: T2 of M0, :: M2 ?

Here's an example: I am writing some function which requires reading, logging, and state. The definition:

gameRoutine :: WriterT [String] (ReaderT Env (State Viable)) NUM

If I want to call some stateFunction :: State Viable NUM

Or even stateWithReaderFunction :: ReaderT Env (State Viable) NUM,

I can use lift:

gameRoutine = do
    x <- lift . lift $ stateFunction
    y <- lift $ stateWithReaderFunction

But how do I call writerFunction :: Writer [String] a ?

How do I call writerStateFunction :: WriterT [String] (State Viable) NUM (the difference between gameRoutine definition is ReaderT layer missing)?

Clearly I don't want to lift their definitions to one of gameRoutine.

Upvotes: 3

Views: 97

Answers (2)

behzad.nouri
behzad.nouri

Reputation: 78011

You can lift a Writer w a into a MonadWriter instance by, something like:

import Control.Monad.Writer (MonadWriter, Writer, runWriter, tell)

lift' :: MonadWriter w m => Writer w a -> m a
lift' wr = do
    let (a, w) = runWriter wr
    tell w    -- manually re-log the log msg
    return a  -- wrap value into new context

then, you may simply write:

gameRoutine :: WriterT [String] (ReaderT Env (State Viable)) NUM
gameRoutine = do
    a <- lift' writerFunction

as long as writerFunction :: Writer [String] a can be specialized with a ~ NUM.

Upvotes: 3

leftaroundabout
leftaroundabout

Reputation: 120741

Well, basically the idea of mtl is that you don't need to do that. Instead of defining writerStateFunction with that specific signature, you define it with a generic signature

writerStateFunction' :: (MonadWriter [String] m, MonadState Viable m)
          => m NUM

Then it doesn't matter that the environment where you try to use this has an extra Reader layer: this doesn't prevent the monad from having both MonadWriter and MonadState functionality.

Upvotes: 5

Related Questions