有人可以解释一下关于 `lens` 库的图表吗?

Could someone explain the diagram about the `lens` library?

如果您浏览 Lens 关于 hackage 的条目、Lens Github 的 repo,甚至 google 关于 Lens,您会发现很多部分参考资料,例如介绍 tutorials/videos 、示例、概述等。由于我已经了解大部分基础知识,因此我正在寻找更完整的参考资料,以帮助我获得有关高级功能的更多知识。换句话说,我仍然不知道 this 是什么意思,也找不到足够完整的资源来解释整个图形。想法?

黑线鳕是最好的深度资源。它们包含所有内容,但一开始可能有点难以导航。只需浏览不同的模块并记下哪些模块在哪里,您很快就会开始找到自己的出路。您 link 的图表也是非常好的模块映射。

但是,既然你说你不理解图形,我假设你不需要高级或完整的参考。该图实际上是 lens 包各部分的非常基本的高级概述。如果你不理解图表,你可能应该稍等一下高级的东西。

阅读本文时请记住,虽然 lens 包最初是作为一个包,好吧,镜头,现在有很多种 光学器件 包,遵守不同的法律,用于不同的事情。 "Optic" 是你用来戳数据结构的类似镜头的东西的总称。

无论如何,这是我阅读图表的方式。

盒子的排列

现在,只需看一下写有光学器件名称的方框顶部。

  1. 顶层FoldSetterlens的基础光学。可以想象,Setter 是一个光学元件,用于设置某个字段的值。我将在这里省略一些示例,因为您说您了解大部分基础知识,但本质上,当您这样做时

    λ> ellen & age .~ 35
    Person { _name = "Ellen", _age = 35 }
    

    那么您已经将 age 用作 Setter

  2. Getter 是一种特殊的 Fold,它允许您从数据结构中获取值。 (Fold 本身只允许您以某种方式将值组合成一个新值,而不是按原样取出它们。)Getter 是一种特殊的 Fold 标记在从 Getter 指向 Fold 的箭头图形。

  3. 如果将 FoldSetter 组合在一起(即组合一种遍历一堆值的方法和一种设置单个值的方法),你会得到TraversalTraversal 是一种瞄准多个元素并允许您设置或修改所有元素的光学元件。

  4. 在图表的下方,如果您将 GetterTraversal 组合,您将得到 Lens。你应该很熟悉,因为镜头通常被称为 "a combination of a getter and a setter",你可以将 Traversal 视为更强大的 Setter.

  5. 我不会假装我非常了解 ReviewPrismIsoEquality 之间的关系。按照我的理解,

    • A Review 是函数 b -> t 的基本包装器,其中 b 应该是 t 结构中的一个字段。所以 Review 接受一个字段值,然后围绕它构建一个完整的结构。这可能看起来很愚蠢,但它实际上是 Getter 的反面,并且对构建棱镜很有用。
    • A Prism 允许您在分支类型中获取和设置值。它对 Either 就像 Lens 对元组一样。你不能只用一个镜头在 Either 里面得到一个值,因为如果它碰到 Left 分支就会爆炸。
    • 一个IsoLensPrism的一个非常强大的组合,它可以让你自由地通过光学观察"both ways"。 Lens 可以让您从高层次查看数据结构的精确部分,而 Iso 还可以让您从数据结构的精确部分向上查看。
    • 一个Equality是...我不知道,抱歉。

插曲:键入签名

Lens s t a b 这样的类型签名一开始可能会让人害怕,所以我会尝试快速解释它的含义。如果你查看 Getter,你会看到它的类型签名是

Getter s a

如果我们从概念上思考 getter 是什么,这可能看起来很熟悉。什么是 getter?它是从数据结构 s 到该结构内部的单个值 a 的函数。例如,函数 Person -> Age 是一个 getter,它从 Person 对象获取年龄。相应的 Getter 只是具有签名

Getter Person Age

就这么简单。 Getter s a 是一种光学器件,可以从 s.

内部获得 a

简化的 Lens' 签名现在应该不那么可怕了。如果你不得不猜测,什么是

Lens' Person Age

?简单!它是一个 Lens,可以 get 和设置 (还记得 lenses 是 getters 和 setter 的组合吗?)Age 字段在 Person 值。因此,您可以将应用于 Getter s a 的相同逻辑应用于 Lens' s a.

但是 s t a b 部分呢?嗯,想想这个类型:

data Person a = { _idField :: a, address :: String }

一个人可以通过一些识别值来识别并且有年龄。比方说你是用名字来识别的

carolyn :: Person String
carolyn = Person "Carolyn" "North Street 12"

如果您有一个函数 toIdNumber :: String -> Int 从字符串生成 ID 号怎么办?你可能想这样做:

λ> carolyn & idField %~ toIdNumber
Person 57123 "North Street 12"

但是如果 idField :: Lens' (Person String) String 你不能,因为那个镜头只能处理 Strings。它不知道将字符串转换为整数并将其粘贴到同一个位置意味着什么。这就是为什么我们有 Lens s t a b.

我看那个签名的方式是"if you give me a function a -> b, I will give you a function s -> t",其中ab都是指镜头的目标,st 引用包含的数据结构。具体例子:如果我们有

idField :: Lens (Person String) (Person Int) String Int

我们可以进行上面的转换。那个镜头知道如何将带有字符串 id 字段的 Person 变成带有 Int id 字段的人。

所以,本质上,Lens s t a b 可以读作 "function" (a -> b) -> (s -> t)。 "Give me a transformation (a -> b) to do on my target, and a data structure s that contains that target, and I will return to you a data structure t where that transformation has been applied."

当然,它实际上不是那个函数——它比那个更强大——但你可以通过将它提供给 over 将它变成那个函数,它是 Setter.[=135 的一部分=]

盒子里的东西

方框里的内容只是最常见的and/or每种光学的核心操作。他们的类型说明了他们所做的很多事情,我将举几个例子来说明我的意思。

一般来说,最上面的项目是你如何构造那种光学元件。例如,Getter 框中最上面的项目是 to 函数,它从任何函数 s -> a 构造一个 Getter。如果您使用 traverse 函数,您可以从 Traversable 免费获得 Traversal

下面是常用的操作。例如,您会在 Getter 下找到 view,这就是您如何使用 Getter 从数据结构中获取某些内容的方式。在 Setter 中,您会发现 overset 高。

(有趣的观察:大多数盒子似乎包含两种双重功能:一种是创建光学元件的方法,一种是使用它们的方法。有趣的是,它们通常具有几乎相同的类型签名,但与每个盒子相比却翻转了其他。示例:

  • to :: (s -> a) -> Getter s a
  • view :: Getter s a -> (s -> a)

  • unto :: (b -> t) -> Review s t a b
  • review :: Review s t a b -> (b -> t)

我现在注意到了一些有趣的事情。)

我如何使用图形

我通常不会像写这篇文章那样仔细研究图形。大多数情况下,我只是查看 SetterGetterTraversalLensPrism 以及它们之间的相对位置,因为这些是光学器件我用的最多。

当我知道我需要做某事时,我通常会快速浏览一下图形,看看哪些框包含与我想做的操作相似的操作。 (例如,如果我有 data Gendered a = Masculine a | Feminine a,那么 _Right 类似于假设的 _Feminine。)当我确定了这一点后,我深入研究了这些模块的黑线鳕(在这个例子中,Prism),搜索我需要的操作

我是如何学习光学的

我学习光学以及如何使用它们,就像我学习所有东西一样。我发现一个真正的问题,光学是一个很好的解决方案,我尝试解决它。我试过了,但失败了,我又试了一次,我寻求帮助,我试了又试了又试了。最终我会成功。然后我尝试一些稍微不同的东西。

通过实际使用事物的本意,我收集了很多关于它们如何工作等方面的有用经验。

奖金:个人误解

在我开始写这篇文章之前,我认为你需要 Prisms 来处理 Maybe 值,因为 Maybe 类型是分支的,不是吗?不完全的!至少不超过 List 类型,你不需要 Prism 来处理它。

因为Maybe类型是零个或一个元素的容器,其实你只需要一个Traversal就可以处理了。当您有两个可以包含不同值的分支时,您开始需要 Prism 的全部功能。 (要 构造 一个 Maybe 值,您仍然需要 Review。)

只有当我开始非常仔细地阅读图表并探索模块以找出光学器件之间实际的、形式上的差异时,我才明白这一点。这就是用我的方法学东西的弊端。只要它有效,我就即兴发挥。有时这会导致迂回的做事方式。

该图是从两种类型之间的弱关系到强关系的流程。

在最弱的情况下,你可以在s类型中折叠a类型的"elements",或者你可以将a设置为b ] 在 s 中,将其更改为 t(当然,ab 可以相同,就像 st).在最底层,你们拥有平等;向上一步,你有同构;然后是透镜和棱镜等

从另一个角度来看,由于关系的要求,它从最适用向下流动到最不适用:有许多类型可以根据一些 a 概念上的折叠 "within"他们;而与 a.

相等或同构的东西要少得多