UP | HOME

Magic Monad Transformer

1. 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.

  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

2. What is the so-called “Magic”

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

  -- | 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

  instance MonadReader AppConfig App where ...

  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.

3. A trivial demo

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

Footnotes: