为什么我们需要 Control.Lens.Reified?是否有什么原因我不能直接将一个Lens
放入容器中?reify
到底是什么意思?
[Lens s t a b]
对于某些目的,使用
是可以接受的。Functor f => [(a -> f b) -> s -> f t]
相反,当您深入其中时,您不会得到一个Lens
,而是得到一个针对某个函子或另一个的专用LensLike
。 ReifiedBlah
newtypes 允许您保留完整的多态性。
从操作上讲,[ReifiedLens s t a b]
是一个函数列表,每个函数都需要一个Functor f
字典,而 forall f. Functor f => [LensLike f s t a b]
是一个函数,它需要一个Functor f
字典并返回一个列表。
至于"reify"的含义,好吧,字典会说些什么,在Haskell中似乎这将转化为相当惊人的特定含义。因此,对此不予评论。
lensHead f [] = pure []
lensHead f (x:xn) = (:xn) <$> f x
并将其用于表达式中:
[lensHead]
lensHead
会自动应用于某些类型参数集合中,此时它不再是一个镜头,因为它在函子中不再是多态的。要记住的是:表达式总是有一些单态类型;所以它不是一个镜头。(你会注意到lens
函数接受Getter
和Setter
类型的参数,这些都是单态类型,出于类似于此的原因。但一个[Getter s a]
并不是一组镜头,因为它们已被专门化为仅getter。)eval
命令。 S表达式以一种可以在程序中操作的方式表示函数,这称为reification。newtype ReifiedLens s t a b = ReifiedLens (Lens s t a b)
将多态值转化为真正的一等值后,其效果与取得一个重新实体化的效果相同,这被称为重新实体化。
为什么这样操作是可行的,即使表达式总是具有单态类型? 因为Rank2Types扩展添加了第三条规则:
ReifiedLens
就是这样的一个排名2函数;所以当你说:
ReifiedLens l
在使用 ReifiedLens
时,您需要将参数封装在类型 lambda 中,并且立即应用于绑定到 lambda 的类型参数。因此,l
实际上只是 eta 扩展。 (编译器可以自由地进行 eta 缩减,直接使用 l
)。
然后,当您这样说:
f (ReifiedLens l) = ...
l
是一个具有多态类型的变量,因此每次使用 l
都会立即隐式地分配所需的类型参数以使表达式能够通过类型检查。因此,一切都按照您预期的方式运作。newtype ReifiedLens s t a b = ReifiedLens { unReify :: Lens s t a b }
ReifiedLens
和 unReify
这两个函数类似于显式类型抽象和应用操作符。这使得编译器能够很好地识别你想要进行抽象和应用的位置,从而避免了与非谓词类型系统相关的问题。
[1] 在 lens
的术语中,这显然被称为“镜头”之外的其他东西;我的所有关于镜头的知识都来自 SPJ 对它们的介绍,所以我没有办法验证这一点。但是,由于多态仍然需要让它作为 getter 和 setter 工作,所以重点还是在此。
lensHead
在lens
术语中不会被视为镜头,而是遍历,因为它需要Applicative f
而不仅仅是Functor f
。 - dfeuer