Haskell镜头:视图没有像结束和设置那样参考?

Haskell lens : view doesn't reference like over and set?

第一次使用lenssetover 很容易,我认为 view 会很简单:使用相同的方案来引用内部部分,但不提供新的值或函数。但是不。 tst3 below gives the error below the code。有人知道怎么回事吗?

-- Testing lenses
tst1 = set (inner . ix 0 . w) 9 outer
tst2 = over (inner . ix 0 . w) (+2) outer
tst3 = view (inner . ix 0 . w) outer       -- this line errors out

    * No instance for (Monoid Int) arising from a use of `ix'
    * In the first argument of `(.)', namely `ix 0'
      In the second argument of `(.)', namely `ix 0 . w'
      In the first argument of `view', namely `(inner . ix 0 . w)'

这里是一些简化的代码说明。

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}

import Control.Lens

data Inner = Inner
    {  _w :: Int
    } deriving (Show, Eq)

data Outer = Outer
    { _inner :: [Inner]
    } deriving Show

outer = Outer
    [
    Inner 0,
    Inner 1,
    Inner 2
    ]

makeLenses ''Inner
makeLenses ''Outer

tst1 = set (inner.ix 0.w) 999 outer
tst2 = over (inner.ix 0.w) (+77) outer
tst3 = view (inner.ix 0.w) outer        -- this errors out

我已经阅读了 view 并且简单的示例看起来像我的,如

>>> let atom = Atom { _element = "C", _point = Point { _x = 1.0, _y = 2.0 } }
>>> view (point . x) atom
1.0

尽管我还没有找到用 ix 索引内部结构的示例。

ix 0 doesn't produce a lens, but a traversal.1

通俗地说,镜头是一条“路径”,它将最终达到一个单一的值(如果你在一个假设的更大的值内跟随它)。遍历是通向 零个或多个 值的路径。您可以 setview 镜头的单个目标。您可以 set 遍历的零个或多个目标(这只是更新所有存在的目标,如果存在零个目标,则为 no-op )。但是 view遍历的目标并不那么简单。

如果view只是简单的遍历,外层结构,给你目标值,那就有问题了。如果有多个目标,它应该如何决定哪个return?如果有零个目标,它就不能 return 任何东西;它必须是部分的。它需要的是一种将 zero-or-more 值压缩为单个值的方法,因此它可以 return 那个。而 Monoid class 恰好提供了执行此操作的工具; mempty 用于如果根本没有任何目标,<> 用于将多个值压缩为一个值。所以 view 遍历 2 实际上需要 Monoid 对 returned 类型的约束,这就是为什么你会抱怨 No instance for (Monoid Int) arising from a use of `ix'.

如果不清楚,您通常可以(使用 .)组合不同类型的光学器件(“lens-ish 事物”的总称,包括透镜、遍历和其他几个)。但是结果具有最少能力的两个输入。因此,即使您的 innerw 是全镜头,将它们与 ix 产生的遍历组合也会产生遍历,而不是镜头。

但在这种情况下,您知道您正在使用 ix。特定类型的遍历 ix 使得最终有零个 或一个 目标,而不是零个 或更多 目标一般的。所以你可以使用 preview instead of view;对于遍历,它将产生一个 Maybe 包含 Just 遍历的第一个目标,或者 Nothing 如果没有的话。 Maybe 正是类型系统在这里认为合适的,因为 ix 不能保证 一个目标值,但不会有更多比一个。

根据我的经验,当我尝试 view 某些东西并得到这个 Monoid 实例错误时,这几乎总是意味着我有一个不能保证结果的光学器件,我实际上应该是使用 preview 得到 Maybe.

简单示例:

λ view (ix 1) [True, False]

<interactive>:16:7: error:
    • No instance for (Monoid Bool) arising from a use of ‘ix’
    • In the first argument of ‘view’, namely ‘(ix 1)’
      In the expression: view (ix 1) [True, False]
      In an equation for ‘it’: it = view (ix 1) [True, False]

λ preview (ix 1) [True, False]
Just False
it :: Maybe Bool

λ preview (ix 1) [True]
Nothing
it :: Maybe Bool

1 ix 不能 return 镜头,因为它应该取一个索引然后表示进入 [=90= 的“路径” ]any 可索引结构(至少可以通过给定的索引类型进行索引)。在 ix 0 中,ix 尚未看到您将要在其上使用它的特定结构,因此无法保证该索引处有值;您甚至可以使用 let i = ix 0 然后多次使用 i 来查看不同的结构!


2 从技术上讲,view是遍历支持的,因为它是折叠支持的,所有遍历都是折叠。折叠知道如何 access zero-or-more 目标,但与遍历不同,它不是“路径”;它只是访问值而不知道它们在结构中的上下文(规范顺序除外)。

主要 hackage page for the lens package 上有一个方便但令人困惑的图表,它试图描述主要功能是如何被各种光学器件“继承”的。这有点让人不知所措......但比分散在许多不同模块中的完整文档要少。

你应该能看到在右上角我们有Setter提供setover,这是被Traversal继承的。而在左上角,我们有 viewMonoid 约束,它由 Traversal 继承。在 mid-left 中,我们有 Getter 有一个版本 view 没有 Monoid 约束,它由 [=57= 继承] 但 不是 Traversal

问题是ix 0是遍历,不是镜头,所以不是关注第0个元素,而是关注[=52的集合=]all个元素是第0个元素。为什么要这样做?好吧,假设 _inner 是一个 [Inner] 类型的列表, 通常 只有一个元素是第 0 个元素,但有时(如果列表为空),有 no 个元素是第 0 个元素。将 ix 0 作为遍历说明了这种可能性。

由于ix 0是一次遍历,它“毒害”了整个视觉inner . ix 0 . w。即使innerw是透镜,ix 0的构图也“只是”一次遍历。

现在,遍历“设置”或“覆盖”都没有问题。如果没有第 0 个元素,则该操作什么都不做。另一方面, 查看 这样的遍历有点问题。如果你期望得到一个元素,你可能得不到。

错误消息的出现是因为 view 试图通过假设结果是 Monoid 来处理遍历。这允许它将遍历的零个、一个或多个结果组合成同一类型的单个 return 值。此功能有点深奥,它使错误消息非常混乱,但您会习惯于看到它。

您可以做几件事。您可以将 view 替换为特殊的视图运算符。通常的视图运算符 ^. 类似于 view,因此它会生成相同的错误消息:

-- parentheses are optional here, but included for clarity
tst4 = outer ^. (inner . ix 0 . w)

但是运算符 ^..^?^?! 都会进行类型检查:

tst5 = outer ^.. (inner . ix 0 . w)
tst6 = outer ^? (inner . ix 0 . w)
tst7 = outer ^?! (inner . ix 0 . w)

(^..)returns所有(零个或多个)遍历结果为列表。第二个 (^?) return 是第一个结果 Maybe,使用 Nothing 表示没有结果。最后的 (^?!) return 直接作为第一个结果,如果结果为零(例如在空列表上使用 head),则会产生错误。