镜头和部分镜头有什么区别?

Chr*_*tin 12 haskell scala scalaz lenses calmm

"透镜"和"部分透镜"在名称和概念上看起来相似.他们有什么不同?在什么情况下我需要使用其中一个?

标记Scala和Haskell,但我欢迎与任何具有镜头库的功能语言相关的解释.

J. *_*son 12

为了描述部分镜头 - 我将在今后称之为Haskell lens命名法,棱镜(除了它们不是!请参见Ørjan的评论) - 我想首先对镜头本身进行不同的观察.

镜头Lens s a表示给定一个s我们可以"聚焦" s在类型的子组件上a,查看它,替换它,以及(如果我们使用镜头系列变体Lens s t a b)甚至改变它的类型.

一种看待这种情况的方法是Lens s a见证一种同构,等价,s以及(r, a)某种未知类型的元组类型r.

Lens s a ====== exists r . s ~ (r, a)
Run Code Online (Sandbox Code Playgroud)

这给了我们所需要的东西,因为我们可以拉a出来,替换它,然后通过等价向后运行,以获得一个新的s更新a.


现在让我们花一点时间通过代数数据类型刷新我们的高中代数.ADT中的两个关键操作是乘法和求和.我们写的类型a * b时,我们有一种由具有项目ab我们写a + b的时候,我们有一个类型由它们的项目无论是 ab.

在Haskell中,我们写a * b(a, b)元组类型.我们写a + bEither a b,两种类型.

产品将数据捆绑在一起,总和代表捆绑选项.产品可以表示只有一个你想要选择的东西(一次),而总和代表失败的想法,因为你希望采取一个选项(在左侧,比方说),而是定居于另一个(沿着右边).

最后,总和和产品是绝对双重的.正如大多数PL所做的那样,它们组合在一起而没有另一个,让你处于一个尴尬的地方.


那么让我们来看看当我们将上面的镜片配方二元化(部分)时会发生什么.

exists r . s ~ (r + a)
Run Code Online (Sandbox Code Playgroud)

这是一个声明,s就是无论是一个类型a 一些其他的事情r.我们有一个类似lens的东西,体现了选择(和失败)的概念,深入到它的核心.

这恰好是一个棱镜(或部分镜头)

Prism s a ====== exists r . s ~ (r + a)
                 exists r . s ~ Either r a
Run Code Online (Sandbox Code Playgroud)

那么这对一些简单的例子有什么用呢?

好吧,考虑一下"无视"列表的棱镜:

uncons :: Prism [a] (a, [a])
Run Code Online (Sandbox Code Playgroud)

它等同于此

head :: exists r . [a] ~ (r + (a, [a]))
Run Code Online (Sandbox Code Playgroud)

r这里需要的是相对明显的:完全失败,因为我们有一个空列表!

为了证实这种类型,a ~ b我们需要编写一种方法来将a转换a为a b和a b转换为a使它们相互反转的方式.让我们写一下,以便通过神话功能来描述我们的棱镜

prism :: (s ~ exists r . Either r a) -> Prism s a

uncons = prism (iso fwd bck) where
  fwd []     = Left () -- failure!
  fwd (a:as) = Right (a, as)
  bck (Left ())       = []
  bck (Right (a, as)) = a:as
Run Code Online (Sandbox Code Playgroud)

这演示了如何使用这种等价(至少在原则上)来创建棱镜,并且还表明当我们使用类似列表的类型之类时,它们应该感觉非常自然.

  • 我不认为"部分镜头"和"棱镜"是完全相同的.请注意,"List`"示例@ ChrisMartin的文档粘贴列表的"第n个元素"不是棱镜.它更像是"最多只有一个目标的遍历",我不认为它在"镜头"中有自己的类型. (2认同)
  • @ChrisMartin这是一个非常不同的镜头库,所以我认为使用不多。 (2认同)

Ørj*_*sen 9

镜头是一种"功能参考",允许您以更大的值提取和/或更新广义的"字段".对于普通的非部分镜头,该场总是需要在那里,对于任何包含类型的值.如果您想要查看可能并非总是存在的"字段"之类的内容,则会出现问题.例如,在"列表的第n个元素"(如Scalaz文档@ChrisMartin粘贴中列出)的情况​​下,列表可能太短.

因此,"部分透镜"将透镜概括为场可以或可以不总是以更大的值存在的情况.

Haskell lens库中至少有三件事你可以认为是"部分镜头",其中没有一件完全符合Scala版本:

  • 普通人Lens的"领域"是一种Maybe类型.
  • A Prism,如@ J.Abrahamson所述.
  • Traversal.

它们都有它们的用途,但前两个太受限制而不包括所有情况,而Traversals"太笼统"了.在这三个中,只有Traversals支持"列表的第n个元素"示例.

  • 对于" Lens给出一个Maybe包装好的价值"版本,镜头规则有什么突破:要有一个合适的镜头,你应该能够将其设置Nothing为删除可选字段,然后将其设置回原来的状态,然后获得回到相同的价值.这对于Map说法(并且Control.Lens.At.at为类似Map容器提供这样的镜头)工作正常,但不适用于列表,其中删除例如第0th个元素不能避免干扰后者.

  • A Prism在某种意义上是构造函数(在Scala中大致为case类)而不是字段的泛化.因此,它所提供的"字段"应该包含重新生成整个结构的所有信息(您可以使用该review函数执行此操作.)

  • A Traversal可以做"列表的第n个元素"就好了,实际上至少有两个不同的函数ix,element它们都适用于此(但与其他容器略有不同).

由于类型类魔术lens,任何PrismLens自动作为a Traversal,而Lens给予一个Maybe包装的可选字段可以通过组合变成Traversal一个普通的可选字段traverse.

但是,a Traversal在某种意义上笼统,因为它不限于单个字段:A Traversal可以包含任意数量的"目标"字段.例如

elements odd
Run Code Online (Sandbox Code Playgroud)

是一个Traversal将愉快地通过列表的所有奇数索引元素,更新和/或从中提取信息的所有元素.

从理论上讲,你可以定义第四种变体("仿射遍历"@ J.Abrahamson提及)我认为可能更接近于Scala的版本,但由于lens图书馆本身以外的技术原因,它们不适合其他库的一部分 - 您必须明确地转换这样的"部分镜头"以使用它的一些Traversal操作.

而且,它不会比普通的Traversals 买得多,因为有一个简单的运算符(^?)只提取遍历的第一个元素.

(据我所知,技术原因是Pointed定义"仿射遍历"所需的类型类不是Applicative普通Traversal用途的超类.)


Chr*_*tin 8

Scalaz文档

下面是Scalaz年代scaladocs LensFamilyPLensFamily,重点增加对diff文件.

镜片:

A镜头系列,提供访问和检索纯功能的手段从型过渡B1到类型B2从类型转换同时记录A1输入A2.scalaz.LensA1 =:= A2和,和的一个方便的别名B1 =:= B2.

术语"字段"不应被限制性地解释为表示类的成员.例如,镜头系列可以解决a的成员资格问题Set.

部分镜头:

部分透镜家庭,提供访问和检索一个纯粹的功能性装置的可选字段从型转变B1键入B2在被同时从型转变的记录A1键入A2.scalaz.PLensA1 =:= A2和,和的一个方便的别名B1 =:= B2.

术语"字段"不应被限制性地解释为表示类的成员.例如,部分镜头族可以解决a的第n个元素List.

符号

对于那些不熟悉scalaz的人,我们应该指出符号类型别名:

type @>[A, B] = Lens[A, B]
type @?>[A, B] = PLens[A, B]
Run Code Online (Sandbox Code Playgroud)

在中缀表示法中,这意味着从类型B记录中检索类型字段的镜头类型A表示为A @> B,并且部分镜头表示为A @?> B.

淘金者

Argonaut(一个JSON库)提供了很多部分镜头的例子,因为JSON的无模式特性意味着尝试从任意JSON值检索某些东西总是有失败的可能性.以下是Argonaut镜头构造功能的几个例子:

  • def jArrayPL: Json @?> JsonArray - 仅在JSON值为数组时检索值
  • def jStringPL: Json @?> JsonString - 仅在JSON值为字符串时检索值
  • def jsonObjectPL(f: JsonField): JsonObject @?> Json - 仅在JSON对象具有该字段时检索值 f
  • def jsonArrayPL(n: Int): JsonArray @?> Json - 仅当JSON数组在索引处具有元素时才检索值 n