Haskell镜头:视图没有像结束和设置那样参考?
Haskell lens : view doesn't reference like over and set?
第一次使用lens
。 set
和 over
很容易,我认为 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
通俗地说,镜头是一条“路径”,它将最终达到一个单一的值(如果你在一个假设的更大的值内跟随它)。遍历是通向 零个或多个 值的路径。您可以 set
或 view
镜头的单个目标。您可以 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 事物”的总称,包括透镜、遍历和其他几个)。但是结果具有最少能力的两个输入。因此,即使您的 inner
和 w
是全镜头,将它们与 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
提供set
和over
,这是被Traversal
继承的。而在左上角,我们有 view
和 Monoid
约束,它由 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
。即使inner
和w
是透镜,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
),则会产生错误。
第一次使用lens
。 set
和 over
很容易,我认为 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
通俗地说,镜头是一条“路径”,它将最终达到一个单一的值(如果你在一个假设的更大的值内跟随它)。遍历是通向 零个或多个 值的路径。您可以 set
或 view
镜头的单个目标。您可以 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 事物”的总称,包括透镜、遍历和其他几个)。但是结果具有最少能力的两个输入。因此,即使您的 inner
和 w
是全镜头,将它们与 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
提供set
和over
,这是被Traversal
继承的。而在左上角,我们有 view
和 Monoid
约束,它由 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
。即使inner
和w
是透镜,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
),则会产生错误。