更新: 根据评论,我已经修复了一些错误,并在[[双方括号]]中添加了一些说明。
下面是你的第一个mMistake
为什么会生效...
棱镜是一种光学装置,聚焦于可能存在于"整体"中的"部分"。[[严格来说,它聚焦于可以用来重建整个整体的那种部分,因此它实际上涉及到可以采用几种替代形式(例如和类型)的整体,其中这个"部分"是其中之一。但是,如果你只是用棱镜进行查看而不是设置,这种额外的功能并不太重要。]]
在你的例子中,_StateRun
和 _Just
都是棱镜。 _Just
棱镜聚焦于 Maybe a
整体的 a
部分。这个 a
可能存在,也可能不存在。如果 Maybe a
的值是某个 x :: a
的 Just x
,则部分 a
存在且其值为 x
,这就是 _Just
聚焦的内容。如果 Maybe a
的值是 Nothing
,那么部分 a
不存在,_Just
就不会进行聚焦。
对于你的棱镜 _StateRun
来说,情况有些类似。如果整体 StateStep
是一个 StateRun x y
值,则 _StateRun
聚焦于表示为 StateRun
构造函数字段的元组的"部分",即 (x, y) :: (Int, Maybe Text)
。另一方面,如果整体 StateStep
是一个 StatePause
,这个部分不存在,棱镜就不会聚焦在任何东西上。
当你组合棱镜(如 _StateRun
和 _Just
)和透镜(如 stStep
和 _2
)时,你创建了一个新的光学装置,它将组合聚焦操作的序列。
正如评论中指出的,这个新光学元件不是棱镜;它只是“遍历”。事实上,它是一种特定类型的遍历,称为“仿射遍历”。一般的遍历可以关注零个或多个部分,而仿射遍历专注于恰好零个(未出现的部分)或一个(唯一的部分)。lens
库没有区分仿射遍历和其他类型的遍历。新光学元件之所以“只是”仿射遍历而不是棱镜,与早期的技术细节有关。一旦您添加了镜头,就无法从单个“部分”重构整个“整体”。如果您仅用光学元件进行查看而不进行设置,则这并不重要。
无论如何,请考虑这个光学元件(仿射遍历):
optic1 = stStep . _StateRun . _2 . _Just
这个光学视野包含一个名为
State
的整体。第一个透镜
stStep
聚焦于它的
StateStep
字段。如果该
StateStep
是一个
StateRun x (Just y)
的值,则
_StateRun
棱镜聚焦于
(x, Just y)
部分,而
_2
透镜进一步聚焦于
Just y
部分,
_Just
棱镜进一步聚焦于
y :: Text
部分。
另一方面,如果
StateStep
字段是
StatePause
,则光学器
optic1
不会聚焦于任何内容(因为第二个组件棱镜
_StateRun
也没有聚焦于任何内容),如果是
StateRun x Nothing
,则光学器
optic1
仍然不会聚焦于任何内容,因为即使
_StateRun
可以聚焦于
(x,Nothing)
,
_2
可以聚焦于
Nothing
,但最后的
_Just
不会聚焦于任何内容,因此整个光学器无法聚焦。
特别地,
_2
透镜处理
StatePause
时不会“误火”,也不会尝试引用缺少的第二个字段或类似的内容。使用
_StateRun
来聚焦于
StateRun
构造函数的字段元组,可以确保如果整个光学器聚焦,则所需字段将存在。
现在,这是您第二个光学器的原因:
optic2 = stStep . _StateRun . _Just . stMMistake
无法工作...
实际上有两个问题。首先,stStep . _StateRun
获取整个State
并关注部分(Int, Maybe Text)
。这不是一个Maybe
值,因此它不能与_Just
棱镜组合。您需要先选择Maybe Text
字段,然后应用_Just
棱镜,所以您实际想要的内容更像是:
optic3 = stStep . _StateRun . stMMistake . _Just
这看起来应该可以正常工作,是吧?stStep
镜头关注的是 StateStep
,_StateRun
棱镜只有在存在一个 StateRun x y
值时才聚焦,而 stMMistake
镜头应该让你聚焦于 y :: Maybe Text
,留下 _Just
以聚焦于 Text
。
不幸的是,用 makePrisms
创建的棱镜并不是这样工作的。 _StateRun
棱镜聚焦于一个带有无名字段的普通元组,并且这些字段需要进一步选择使用 _1
、_2
等,而不能使用试图选择命名字段的 stMMistake
。
事实上,如果你仔细查看 stMMistake
,你会发现它本身就是一个光学器(一个仿射遍历,或者就像 lens
库所说的,只是一个遍历),它接受整个StateStep
并直接聚焦于 _stMMistake
字段部分,而不需要指定构造函数。因此,你实际上可以使用 stMMistake
代替 _StateStepRun . _2
,它们应该可以完全一样地工作:
mMistake = st ^? stStep . _StateStepRun . _2 . _Just
mMistake = st ^? stStep . stMMistake . _Just
这不是透镜的一些基本理论特性或其他什么东西。这只是
makeLenses
和
makePrisms
使用的命名和类型约定。使用
makeLenses
,您可以创建聚焦于数据结构中命名字段的光学工具。如果只有一个构造函数:
data Foo = Bar { _x :: Int, _y :: Double }
或者如果有多个构造函数但该字段在所有构造函数中都存在:
data Foo = Bar { _x :: Int, _y :: Double }
| Baz { _x :: Int, _z :: Char }
那么字段光学(在此示例中为 x
)是一种始终聚焦于该字段的镜头。如果有多个构造函数,其中一些具有该字段而另一些没有:
data Foo = Bar { _x :: Int, _y :: Double }
| Baz { _x :: Int, _z :: Char }
| Quux { _f :: Int -> Double }
那么,字段光学(在这里是x
)是一种聚焦于字段的光学(遍历),但仅在其存在时进行(即值为Bar
或Baz
时,而不是Quux
时)。
另一方面,makePrisms
总是创建构造器棱镜,用于聚焦未命名元组形式的字段,需要使用_1
、_2
等引用这些字段,而不是使用构造函数内部的任何字段名称。
也许这回答了您的问题?
_2
这个名字感到困扰?你说它很有道理地得到了一个 tuple,而_2
是用来访问 tuple 中第二个元素的。Haskell 的类型系统非常严格,不会像你的第二个例子那样允许你跳过步骤。 - Silvio MayolostMMistake
来在_StateStepRun
之后深入访问。_2
并不完全能表达原始数据类型不包含元组的情况。 - ruben.moor