为什么我们需要Control.Lens.Reified?

13

为什么我们需要 Control.Lens.Reified?是否有什么原因我不能直接将一个Lens放入容器中?reify到底是什么意思?

2个回答

13
我们需要具象化镜头,因为Haskell的类型系统是预测性的。我不知道这究竟意味着什么技术细节,但它禁止像以下这样的类型:
```haskell data Foo a = MkFoo { foo :: a -> Bool } ```
这是因为 `a` 出现在了正常的和消极的位置上。
[Lens s t a b]

对于某些目的,使用

是可以接受的


Functor f => [(a -> f b) -> s -> f t]

相反,当您深入其中时,您不会得到一个Lens,而是得到一个针对某个函子或另一个的专用LensLikeReifiedBlah newtypes 允许您保留完整的多态性。

从操作上讲,[ReifiedLens s t a b]是一个函数列表,每个函数都需要一个Functor f 字典,而 forall f. Functor f => [LensLike f s t a b]是一个函数,它需要一个Functor f 字典并返回一个列表。

至于"reify"的含义,好吧,字典会说些什么,在Haskell中似乎这将转化为相当惊人的特定含义。因此,对此不予评论。


8
问题在于,在Haskell中,类型抽象和应用是完全隐含的;编译器应该在需要的地方插入它们。各种试图设计“非预测”扩展的尝试,其中编译器会做出聪明的猜测放置它们,都失败了;因此,最安全的方法是依靠Haskell 98规则:
  • 类型抽象仅出现在函数定义的顶层。
  • 每当使用具有多态类型的变量时,立即进行类型应用。
所以如果我定义一个简单的镜头:[1]
lensHead f [] = pure []
lensHead f (x:xn) = (:xn) <$> f x

并将其用于表达式中:

[lensHead]
lensHead会自动应用于某些类型参数集合中,此时它不再是一个镜头,因为它在函子中不再是多态的。要记住的是:表达式总是有一些单态类型;所以它不是一个镜头。(你会注意到lens函数接受GetterSetter类型的参数,这些都是单态类型,出于类似于此的原因。但一个[Getter s a]并不是一组镜头,因为它们已被专门化为仅getter。)
“reify” 是什么意思?词典上的定义是“使实际”。在哲学中,“reifying”指的是将某物视为真实的行为(而非理想或抽象)。在编程中,它通常指将原本不能被视为数据结构的东西表示为数据结构。例如,在早期的Lisp中,没有第一类函数;相反,您必须使用S表达式传递“函数”,并在需要调用函数时使用eval命令。 S表达式以一种可以在程序中操作的方式表示函数,这称为reification
在Haskell中,我们通常不需要像Lisp S表达式这样复杂的再现策略,部分原因是语言的设计避免了这种需要;但因为
newtype ReifiedLens s t a b = ReifiedLens (Lens s t a b)

将多态值转化为真正的一等值后,其效果与取得一个重新实体化的效果相同,这被称为重新实体化。

为什么这样操作是可行的,即使表达式总是具有单态类型? 因为Rank2Types扩展添加了第三条规则:

  • 某些函数的参数顶层出现类型抽象,称为排名2类型。

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 }

ReifiedLensunReify 这两个函数类似于显式类型抽象和应用操作符。这使得编译器能够很好地识别你想要进行抽象和应用的位置,从而避免了与非谓词类型系统相关的问题。

[1] 在 lens 的术语中,这显然被称为“镜头”之外的其他东西;我的所有关于镜头的知识都来自 SPJ 对它们的介绍,所以我没有办法验证这一点。但是,由于多态仍然需要让它作为 getter 和 setter 工作,所以重点还是在此。


1
我喜欢这个解释;但我希望你的回答没有错过最初的新问题投票浪潮。有关类型lambda的即时应用似乎很可能与高阶类型比一般的不可预测性更容易处理的原因有关。一个小小的争议:我认为lensHeadlens术语中不会被视为镜头,而是遍历,因为它需要Applicative f而不仅仅是Functor f - dfeuer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接