这是一个非常好的问题,需要进行一些详细的解释。
首先,我想温柔地纠正您一个错误:最近版本中
lens
包中Setter的类型为
type Setter s t a b = (a -> Identity b) -> s -> Identity t
目前没有找到任何Functor
。
然而,这并不否定你的问题。为什么类型不简单地为
type Setter s t a b = (a -> b) -> s -> t
首先,我们需要讲解 Lens
。
Lens
Lens
是一种类型,允许我们执行 getter 和 setter 操作。这两个操作结合在一起形成一个美妙的函数式引用。
Lens
类型的一个简单选择是:
type Getter s a = s -> a
type Setter s t a b = (a -> b) -> s -> t
type Lens s t a b = (Getter s a, Setter s t a b)
然而,这种类型深感不满意。
- 它不能与
.
组合,而这可能是lens
包的最佳卖点。
- 构建大量元组,只为稍后将它们分解,这样会浪费很多内存。
- 最重要的问题:接受getter(例如
view
)和setter(例如over
)的函数无法使用镜头,因为它们的类型非常不同。
如果不能解决上述问题,那么写库还有什么意义呢?我们不希望用户必须经常考虑光学学科的UML层次结构,每次移动上下文时都要调整函数调用。
那么目前的问题是:我们能否写出一个类型为Lens
的类型,使其自动成为Getter
和Setter
?为此,我们必须转换Getter
和Setter
的类型。
Getter
首先注意,s -> a
等效于forall r. (a -> r) -> s -> r
。这种转换为续定式样式远非明显。您可能能够将此转换直观地理解为:“类型为s -> a
的函数承诺,给定任何s
,您都可以向我提供a
。但是,这应该等同于一个承诺,即给定将a
映射到r
的函数,您也可以向我提供将s
映射到r
的函数。”也许?也许不是。这里可能涉及一种信仰跳跃。
现在定义newtype Const r a = Const r deriving Functor
。请注意,Const r a
在数学上和运行时都与r
相同。
现在请注意:type Getter s a = forall r. (a -> r) -> s -> r
可以重写为type Getter s t a b = forall r. (a -> Const r b) -> s -> Const r t
。虽然我们为自己引入了新的类型变量和思维痛苦,但这种类型与我们最初的类型(s -> a
)在数学上仍然相同。
Setter
全部内容
完成这些工作后,我们能否将setter和getter合并为一个单一的Lens
类型?
type Setter s t a b = (a -> Identity b) -> s -> Identity t
type Getter s t a b = forall r. (a -> Const r b) -> s -> Const r t
这是Haskell,我们可以将Identity
或Const
的选择抽象为一个量化变量。正如lens wiki所说,所有Const
和Identity
共同之处在于它们都是Functor
。然后我们选择将其作为这些类型统一的一种方式:
type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
(选择 Functor 的其他原因也很多,例如可以使用自由定理证明函数引用的定律。但是为了节省时间,我们在这里稍微做了些简化处理。)那个 `forall f` 就像上面的 `forall r` —— 它让类型的使用者去选择如何填充变量。填入 `Identity` 会得到一个 setter。填入 `Const a` 则会得到一个 getter。正是通过沿途小心翼翼地进行小的转换,我们才能到达这一步。
注意事项
需要注意的是,这个推导并不是 `lens` 包最初的动机。正如 推导维基页面 所解释的那样,你可以从 `.` 和某些函数的有趣行为开始,然后推出光学元素。但是我认为我们创造的这条路径更好地解释了你提出的问题,这也是我一开始遇到的一个大问题。我还想向你介绍 茶话会上的光学元素,它提供了另外一个推导。
我认为这些多个推导是一件好事,也是检测 `lens` 设计是否健康的一种方法。我们能够从不同角度得出相同的优雅解决方案,这意味着这种抽象是健壮的,并且不同的直觉和数学都对其提供了良好的支持。
我也稍微欺骗了一下关于最近 `lens` 中 Setter 的类型。实际上,它是
type Setter s t a b = forall f. Settable f => (a -> f b) -> s -> t b
这是抽象化光学类型中高阶类型的另一个示例,以提供更好的库使用体验。几乎总是会将
f
实例化为
Identity
,因为有一个
instance Settable Identity
。然而,偶尔您可能想要将setter传递给
backwards
函数,这会将
f
固定为
Backwards Identity
。我们可以将此段落归类为“关于
lens
的更多信息,您可能并不想知道”。
zlens f p = fmap (const p) $ f 0 -- zlens :: Lens' Point Int
。 - qweSetter
,我认为你无法通过欺骗来绕过它。但当然,并非总是可用。 - dfeuer