为什么 Reader 基于 ReaderT 实施?
Why Reader implemented based ReaderT?
我发现Reader
是在ReaderT
的基础上使用Identity
实现的。为什么不先做Reader
,再做ReaderT
呢?是否有具体理由以这种方式实施?
它们是相同的数据类型,以便在 Reader
和 ReaderT
之间共享尽可能多的代码。目前,只有 runReader
、mapReader
和 withReader
有任何特殊情况。 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
需要你在事情崩溃之前进一步探索。
我发现Reader
是在ReaderT
的基础上使用Identity
实现的。为什么不先做Reader
,再做ReaderT
呢?是否有具体理由以这种方式实施?
它们是相同的数据类型,以便在 Reader
和 ReaderT
之间共享尽可能多的代码。目前,只有 runReader
、mapReader
和 withReader
有任何特殊情况。 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
需要你在事情崩溃之前进一步探索。