2

我最近正在阅读 Snap 的源代码,这很棒,但是当我继续阅读 Snaplet Handler 源代码时,我陷入了 RST、Lensed 和 LensT 的抽象。

newtype RST r s m a = RST { runRST :: r -> s -> m (a, s) }
newtype LensT b v s m a = LensT (RST (Lens b v) s m a)
newtype Handler b v a = Handler (LensT (Snaplet b) (Snaplet v) (Snaplet b) Snap a)

现在 LensT 更改为 Lensed

newtype Lensed b v m a = Lensed { unlensed :: ALens' b v -> v -> b -> m (a, v, b) }

Snaplet Design所说的We switched to a slightly more specialized monad formulation called Lensed that avoids traversal of the whole state hierarchy when the state is manipulated.

感觉Snap和Snaplet Handler的实现有差距,关键是RST、LensT和Lensed,有没有参考文档可以帮帮我?

4

1 回答 1

2

TL;DR - 没有差距。您粘贴的处理程序定义已过期。当前使用Lensed。

长答案:我们没有任何关于此的文档,因为它是一个低级的实现细节——即所有这些都对最终用户完全隐藏。卡尔是对的,RST 只是RWST减去 W,但让我们做一些更深入的调查。使用您在上面显示的类型,我们将 RST 的定义替换为 LensT 定义。这给了我们以下替换:

r = Lens b v
s = s
m = m
a = a

有了它,我们可以轻松编写扩展的 LensT 定义:

newtype LensT b v s m a = LensT { unlensT :: Lens b v -> s -> m (a, s) }

将其与 Lensed 进行比较:

newtype Lensed b v m a = Lensed { unlensed :: ALens' b v -> v -> b -> m (a, v, b) }

如果我们假设Lens b vandALens' b v是可互换的(从概念上讲,它们是可互换的),那么您可以看到这种等价性:

Lensed b v m a = LensT b v (b,v) m a

现在我们看到了问题的症结所在。LensT 是比 Lensed 更通用的构造。LensT 允许您s任意选择。镜头修复s完全由b和决定v。现在我们了解了区别,问题是这两个结构在 Snap 中是如何实际使用的。快速 grepTypes.hs向我们展示了 Handler 使用 Lensed 而 Initializer 使用 LensT。(附注:您为 Handler 提供的定义不是我们当前使用的定义。)以下是定义的重要部分。

Handler b v a = Handler (L.Lensed (Snaplet b) (Snaplet v) ...)
Initializer b v a = Initializer (LT.LensT (Snaplet b) (Snaplet v) (InitializerState b)...)

Initializer 使用更通用的 LensT 构造,因为它需要sbe ,其中包含与andInitializerState无关的额外信息。事实上,Initializer 存在的全部意义在于便于构造将用作 Handler 初始状态的 a。Initializer 在您的应用程序启动时运行,但 Handler 是您的应用程序在其中运行的。我们希望 Handler 尽可能高效,因此我们创建了 Lensed 来针对 Handler 的需求进行优化。您可能会说这是过早的优化,但由于其他人为我做了,我不会拒绝。bvb

您可能想知道为什么我们甚至还有 Lensed 和 LensT。它们只用在一个地方,所以我们可以将它们的定义分别替换为 Handler 和 Initializer 。那是因为我们一开始并没有 Lensed。Handler 和 Initializer 都是用 LensT 编写的,因此消除重复代码是一个完全合理的抽象。Lensed 是后来出现的,因为无论如何它们都是新类型,所以这些抽象层强制实现零运行时成本。

于 2013-10-09T16:46:54.407 回答