Magic Monad Transformer

April 7, 2012, Tags: monad, mtl, haskell

Monad-Transformer

The code fragment below is from chapter 18 Monad Transform of <Real World Haskell>1.

When the first time I read this example, I was confused with how it is possible to use ask of MonadReader (line 6) and get of MonadState (line 13) functions in the same App Monad content.

The only reasonable explanation is that App is both MonadReader and MonadState. While looking at App type definition (line 1), seems it is not possible.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type App = ReaderT AppConfig (StateT AppState IO)

constrainedCount :: Int -> FilePath -> App [(FilePath, Int)]
constrainedCount curDepth path = do
  contents <- liftIO . listDirectory $ path
  cfg <- ask
  rest <- forM contents $ \name -> do
            let newPath = path </> name
            isDir <- liftIO $ doesDirectoryExist newPath
            if isDir && curDepth < cfgMaxDepth cfg
              then do
                let newDepth = curDepth + 1
                st <- get
                when (stDeepestReached st < newDepth) $
                  put st { stDeepestReached = newDepth }
                constrainedCount newDepth newPath
              else return []
  return $ (path, length contents) : concat rest

What is the so-called “Magic”

I turn to the source of package mtl2 and finding following implementations.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

-- | ReaderT
instance (Monad m) => MonadReader r (ReaderT r m) where
    ask = ReaderT.ask
    local = ReaderT.local

-- | StateT
instance (Monad m) => MonadState s (Lazy.StateT s m) where
    get = Lazy.get
    put = Lazy.put

-- | Combine ReaderT and StataT
instance (MonadState s m) => MonadState s (ReaderT r m) where
    get = lift get
    put = lift . put

If we do a substitution, will get

1. instance MonadReader AppConfig App where ...

2. instance (MonadState AppState (StateT AppState IO)
          => MonadState AppState (ReaderT AppConfig (StateT AppState IO)) where ...
   ~> instance MonadState AppState App where ...

Therefore App is both MonadReader and MonadState.

A trivial demo

I made a very trivial sample 3 demostrating combine ReaderT and StateT.