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.