为什么 Reader 基于 ReaderT 实施?

Why Reader implemented based ReaderT?

https://hackage.haskell.org/package/transformers-0.6.0.2/docs/src/Control.Monad.Trans.Reader.html#ReaderT

我发现Reader是在ReaderT的基础上使用Identity实现的。为什么不先做Reader,再做ReaderT呢?是否有具体理由以这种方式实施?

它们是相同的数据类型,以便在 ReaderReaderT 之间共享尽可能多的代码。目前,只有 runReadermapReaderwithReader 有任何特殊情况。 withReader 没有任何独特的代码,它只是一种类型特化,所以只有两个函数实际上为 Reader 做任何特殊的事情,而不是 ReaderT.

您可能会在查看模块导出时认为购买的东西不多,但实际上是。为 ReaderT 定义了很多实例,Reader 也自动拥有,因为它们是同一类型。因此,对于两者只有一种基础类型,实际上代码要少一些。

鉴于此,您的问题归结为询问为什么 Reader 是在 ReaderT 之上实现的,而不是相反。为此,嗯,这是唯一可行的方法。

让我们试着换个方向,看看哪里出了问题。

newtype Reader r a = Reader (r -> a)
type ReaderT r m a = Reader r (m a)

是的,好的。内联别名并去掉新类型包装,ReaderT r m a 等同于 r -> m a,这是应该的。现在让我们前进到 Functor 实例:

instance Functor (Reader r) where
    fmap f (Reader g) = Reader (f . g)

是的,对于 Reader 的定义,它是 Functor 的唯一可能实例。由于 ReaderT 是相同的基础类型,它还为 ReaderT 提供了 Functor 的实例。除了出现了可怕的错误。如果您将第二个参数和结果类型固定为您期望的类型,fmap 专门针对类型 (m a -> m b) -> ReaderT r m a -> ReaderT r m b。那根本不对。 fmap 的第一个参数的类型应该是 (a -> b)。两边那个m肯定不应该有

但这正是当您尝试根据 Reader 而不是相反的方式来实施 ReaderT 时发生的情况。为了在两种类型之间共享 Functor(以及更多)的代码,每种类型中的最后一个类型变量必须与基础类型中的相同。当 ReaderT 基于 Reader 时,这是不可能的。它必须引入一个额外的类型变量,并且从所有替换中获得正确结果的唯一方法是使 Reader r a 中的 a 引用与 [=42 不同的东西=] 在 ReaderT r m a 中。事实证明,这与在两种类型之间共享 Functor 等更高级的实例不兼容。

有趣的是,您选择了 Reader 的最佳情况,因为可以让类型完全对齐。例如,如果您尝试将 StateT 基于 State,事情就会失败得更快。甚至没有办法编写一个类型别名来添加 m 参数并扩展到该对的正确内容。 Reader 需要你在事情崩溃之前进一步探索。