Monad变压器的解剖学

Mat*_*ick 9 monads haskell monad-transformers

我正在尝试学习monad变换器,基于标准的Haskell库(mtl?变换器?不确定哪一个来自我的Haskell平台下载 - 7.4.1).

我相信我注意到的是每个monad变换器定义的通用结构:

  1. 基本类型('基础')

    • Monad实例
  2. 变压器类型('BaseT')

    • Monad实例

    • MonadTrans实例

    • MonadIO实例

  3. 变压器类('MonadBase')

    • 一些操作

    • 其他'BaseT'的实例

因此,例如,对于Writer monad,会有:

  • 一个Writer数据类型/ newtype/type,带有Monad实例
  • WriterT数据类型/ newtype/type,包含Monad,MonadTrans和MonadIO实例
  • 一个MonadWriter类,以及StateT,ReaderT,IdentityT,...的这个类的实例

这是monad变形金刚的组织方式吗?我错过了什么/我有任何不正确的细节吗?

这个问题的动机是搞清楚:

  1. "BaseT"和相应的"MonadBase"和"Base"之间的关系和区别是什么
  2. 是否都需要这三个
  3. MonadTrans如何相关以及它的目的是什么

Yur*_*ras 4

mtl包没有实现 monad 转换器。至少 WriterT刚刚transformers.

transformerspackage Implements WriterT,它本身就是一个 monad 转换器。Writer只是一个别名:

type Writer w = WriterT w Identity
Run Code Online (Sandbox Code Playgroud)

有些库可以Writer单独实现,但无论如何它只是WriterT. (Identity是一个简单的 monad,它没有任何额外的行为。)

MonadTrans允许您将底层 monad 包装到转换后的 monad 中。您可以没有它,但您将需要执行手动包装(请参阅MonadTrans实例定义以了解WriterT如何执行此操作的示例)。MonadTrans当您不知道变压器的实际类型时,您真正需要的唯一用例。

MonadWriter是在 中声明的类型类mtl。它的方法(、 和writerpass与的函数相同。它允许(自动!)通过变压器堆栈进行计算,即使您不知道堆栈中变压器的确切类型(甚至数量!)。telllistenWriterTWriterT

因此,WriterT这是唯一“必需”的类型。

对于其他 monad 转换器,它是相同的:BaseT是一个转换器,Base是一个没有底层 monad 的 monad,并且MonadBase是一个类型类——所有 monad 的类,BaseT在转换器堆栈中的某个位置。

添加:

你可以在RWH 书中找到很好的解释

这是一个基本示例:

import Control.Monad.Trans
import Control.Monad.Trans.Writer
import Control.Monad.Trans.Reader hiding (ask)

-- `ask` from transformers
-- ask :: Monad m => ReaderT r m r
import qualified Control.Monad.Trans.Reader as TransReader (ask)

-- `ask` from mtl
-- ask :: MonadReader r m => m r
import qualified Control.Monad.Reader as MtlReader (ask)

-- Our monad transformer stack:
-- It supports reading Int and writing String
type M m a = WriterT String (ReaderT Int m) a

-- Run our monad
runM :: Monad m => Int -> M m a -> m (a, String)
runM i action = runReaderT (runWriterT action) i

test :: Monad m => M m Int
test = do
  tell "hello"
  -- v <- TransReader.ask     -- (I) will not compile
  v1 <- lift TransReader.ask  -- (II) ok
  v2 <- MtlReader.ask         -- (III) ok
  return (v1 + v2)

main :: IO ()
main = runM 123 test >>= print
Run Code Online (Sandbox Code Playgroud)

请注意,这(I)将被编译器拒绝(尝试查看错误消息!)。但(II)由于MonadTrans(“显式提升”),可以编译。感谢MonadReader(III)开箱即用(“隐式提升”)。请阅读 RWH 书籍以了解其工作原理。

(在示例中,我们ask从两个不同的模块导入,这就是我们需要限定导入的原因。通常您一次只会使用其中一个。)

而且我也不是故意要特别问的Writer

不确定我理解... ReaderState其他人使用相同的模式。替换WriterState,您将得到解释State

  • 感谢您的回复!然而,我认为对于对 monad 转换器知之甚少的人来说——我:)——答案会通过一些额外的清晰度和细节而得到改善。也许添加一些例子也会有帮助;例如,演示 `MonadTrans` 和 `MonadBase` 之间的区别。另外,我并不是想专门询问“Writer”。也就是说,我并不是想对这篇文章持消极态度——它很有帮助,值得赞赏。谢谢! (2认同)
  • 嗯,还是不太顺利。我有点明白你写的内容,但如果我自己实现一个变压器,我不知道从哪里开始——我不知道关键概念是什么,也不知道它们如何组合在一起。估计只需要付出一些努力就可以了! (2认同)