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.