有人能解释一下关于“lens”库的图表吗?

18
如果您查看Hackage上的Lens条目、Lens Github仓库,甚至在Google上搜索Lens,您会发现很多部分性的参考资料,例如入门教程/视频、示例、概述等。由于我已经了解了大部分基础知识,所以我正在寻找一个更完整的参考资料,以帮助我获取更多关于高级功能的知识。换句话说,我仍然不知道这个图形意味着什么,并且找不到足够完整的资源来解释整个图形。有什么好的建议吗?
2个回答

34

Haddocks是最深入的资源,包含所有内容,但刚开始使用可能有点难以导航。只需浏览不同模块,做好心理笔记,很快就能找到方向。你链接的图表也是各个模块的非常好的地图。

然而,既然你说你不理解图形,那我就假设你不需要高级或完整的参考。该图实际上是lens软件包部件的一个非常基本的高层概述。如果你不理解图表,最好还是先等一段时间再进行高级操作。

阅读此内容时,请记住,虽然lens软件包最初是“镜头”软件包,但现在软件包中有许多种不同类型的光学工具,它们遵循不同的法则并用于不同的用途。“光学工具”是您用于探测数据结构的类似镜头的通用名称。

无论如何,以下是我如何阅读该图的方法。

框的排列

现在,只看写有光学器名称的框的顶部部分。

  1. 顶层,即FoldSetter,是lens的基本光学器。正如你可以想象的那样,Setter是一种设置某个字段值的光学器。这里省略了一堆示例,因为你说你已经掌握了大部分基础知识,但本质上当你执行

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

    如果你曾经用过 age 作为一个 Setter,那么你已经了解了它。

    Getter 是一种特殊的 Fold,允许你从数据结构中获取值。(Fold 只允许你将值以某种方式组合成新值,而不是将它们取出。) 在图表中,从 Getter 指向 Fold 的箭头标注了 Getter 是一种特殊的 Fold

    如果你将一个 Fold 和一个 Setter (即将循环遍历一堆值和设置单个值的方法组合在一起),你就会得到一个 TraversalTraversal 是一个瞄准多个元素的光学器,并允许你设置或修改它们。

    进一步地,如果你将一个 Getter 与一个 Traversal 结合起来,你就会得到一个 Lens。这对你来说应该很熟悉,因为“getter 和 setter 的组合”通常被称为镜头,而你可以将 Traversal 视为更强大的 Setter

    我不会假装我对 ReviewPrismIsoEquality 之间的关系非常了解。据我所知,

    • Review 是一个围绕着函数 b -> t 的基本包装器,其中 b 应该是 t 结构中的一个字段。因此,Review 接受单个字段值,然后构造一个整个结构。这可能看起来很愚蠢,但它实际上是 Getter 的相反,用于构建棱镜。
    • Prism 允许您在分支类型中获取和设置值。它对于 Either 来说就像 Lens 对于元组一样。您无法仅使用镜头获取 Either 中的值,因为如果它遇到 Left 分支,它将会崩溃。
    • IsoLensPrism 的非常强大的组合,允许您自由地通过光学器 "正反两面" 地查看数据。虽然 Lens 让您从高层次上看到数据结构的精确部分,但 Iso 还允许您从数据结构的精确部分向高层次查看。
    • 我不知道 Equality 是什么,抱歉。

    中断: 类型签名

    类型签名如 Lens s t a b 一开始可能会让人感到害怕,所以我将尽快解释其含义。如果你查看 Getter,你会发现它的类型签名是:

    Getter s a
    

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

    Getter Person Age
    

    就是这么简单。一个 Getter s a 是一种能够从 s 内部获取 a 的光学元件。

    现在,经过简化的 Lens' 签名应该不那么可怕了。如果你必须猜测,a 是什么?

    Lens' Person Age
    

    很简单!它是一个Lens,可以获取和设置(记得镜头是获取器和设置器的组合吗?)Person 值内的 Age 字段。因此,您应用于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 ,你不能将字符串转换为整数并放入相同的位置,因为该镜头只能处理 String。这就是为什么我们需要 Lens s t a b

    我理解这个签名的方式是“如果你给我一个函数 a -> b,我会给你一个函数 s -> t”,其中 ab 都指代镜头的目标,而 st 指代包含数据结构。具体例子:如果我们有:

    idField :: Lens (Person String) (Person Int) String Int
    
    我们可以进行上述的转换。该镜头知道如何将具有字符串id字段的Person转换为具有整数id字段的person。
    因此,本质上,Lens s t a b可以被解读为一个“函数”(a -> b) -> (s -> t)。“给我一个在目标上执行的转换(a -> b)以及包含该目标的数据结构s,我将返回您一个应用了该转换的数据结构t”。
    当然,它实际上不是那个函数——它比那更强大——但是通过将其提供给Setter中的over,您可以将其变成该函数。

    盒子的内容

    盒子的内容仅仅是每种视觉类型最常见和/或核心的操作。它们的类型说明很多关于它们所做的事情,我会选择一些例子来说明我的意思。
    通常,顶部项目是您构建该类型的视觉方式。例如,在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类似。)在确定后,我会深入研究这些模块的Haddocks(在这个例子中,是Prism),寻找我需要的操作。

    我如何学习光学器件

    我学习光学器件和如何使用它们的方式与我学习所有东西的方式相同。我找到一个光学器件是一个好的解决方案的实际问题,并尝试解决它。我尝试并失败,然后再次尝试并寻求帮助,我不断尝试、不断尝试。最终我会成功。然后我尝试一些稍微不同的东西。

    通过实际使用它们的方式,我收集了很多有用的使用经验,知道它们如何工作等等。

    额外:一个个人误解

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

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

    我仅当我开始仔细阅读图表并探索模块以找出光学器件之间的实际、正式差异时才明白了这一点。这是使用我的学习方法的缺点。只要它发挥作用,我就凭直觉做事。有时这会导致绕远路。


1
只是想说我还在消化你的答案并阅读了很多材料,所以我还没有评论,但我非常感激它并且它对我有很大帮助。谢谢! - MaiaVictor
@Viclib 花费一些时间是可以预料的。这些都是我在断断续续地学习了几个月后才掌握的知识。 :) - kqr

1
这张图展示了两种类型间关系的强度与可操作性之间的关系。最弱的关系是在类型s中对类型a的“元素”进行折叠,或将类型a设置为类型s中的类型b,从而将其改变为类型t。在底部是相等;上一步是同构;然后是透镜和棱镜等。换个角度看,它从最适用的向最不适用的流动,这是由于关系的要求:有许多类型可以在某些概念上“内含”地折叠;而等于或同构于类型a的东西要少得多。

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