作为练习,我一直在重新实现一些常见的单子和它们相应的转换器;以下是我定义的一些类型:
newtype Writer w a = Writer { runWriter :: (w, a) }
newtype WriterT w m a = WriterT { runWriterT :: m (Writer w a) }
newtype Maybe a = Just a | Nothing
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
当我得到一个变压器"包装"一个单子在一个外部单子m
;根据这种直觉,我尝试以类似的方式定义ReaderT
变压器:
newtype Reader r a = Reader { runReader :: r -> a }
newtype ReaderT r m a = ReaderT { runReaderT :: m (Reader r a) }
然而,我在实现它的monad实例时遇到了困难。通过查看ReaderT
的库定义,我看到它被定义为
newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }
除了它不使用Reader
(可以用ReaderT
来定义),为什么它被定义为r -> m a
而不是m (r -> a)
?我认为它应该包装内部Reader
(即r -> a
)与外部单子m
获得m (r -> a)
,而不是r -> m a
m (r -> a)
意味着你:
-
首先在单子中执行一些操作,然后,
-
动作完成后,对reader值执行纯计算。
但是你需要能够使用ask
:
ask :: Monad m => ReaderT r m r
使用ask
,您可以:
首先读取值,
然后执行一些单元操作,作用于值。
这就是为什么r -> m a
有意义而m (r -> a)
没有意义。
如果有帮助,可以将其视为与writer单子"对称"。对于写单子,单子在外面,结果在里面。对于阅读器单子,输入在外面,单子在里面。这种对称经常出现。