rub*_*oor 5 haskell record lenses haskell-lens
我正在尝试使用 Haskell 中的透镜和棱镜访问嵌套记录:
import Data.Text (Text)
import Control.Lens.TH
data State = State
{ _stDone :: Bool
, _stStep :: StateStep
}
data StateStep
= StatePause
| StateRun
{ _stCounter :: Int
, _stMMistake :: Maybe Text
}
makeLenses ''State
makeLenses ''StateStep
makePrisms ''StateStep
main :: IO ()
main = do
let st = State False $ StateRun 0 Nothing
-- works, but the `_2` seems weird
mMistake = st ^? stStep . _StateStepRun . _2 . _Just
-- why not something like (the following does not compile)
mMistake = st ^. stStep . _StateStepRun . _Just . stMMistake
Run Code Online (Sandbox Code Playgroud)
这条有效的线路留下了一些悬而未决的问题。我不确定类型是否巧合匹配。该字段_stMMistake有 type Maybe Text,但是呢
let st = State False StatePause
?我缺少明确的join.
我对棱镜的工作原理一无所知。虽然棱镜给我一个元组似乎是合乎逻辑的,但同时我期望一些可组合的东西,因为我可以使用透镜更深入地了解我的嵌套结构。我是否必须为此手动派生实例?
更新: 根据评论,我修复了一些错误,并在 [[双方括号]] 中添加了一些旁白。
这是你的第一个作品的方式/原因mMistake......
棱镜是一种聚焦于“整体”中可能存在或不存在的“部分”的光学器件。[[从技术上讲,它侧重于可用于重建整个整体的部分,因此它实际上涉及可以采用多种替代形式(如求和类型的情况)的整体,其中“部分” “是这些替代形式之一。但是,如果您仅使用棱镜进行查看而不是设置,则此附加功能并不是太重要。]]
在你的例子中, 和_StateRun都是_Just棱镜。棱镜_Just聚焦于整体a的一部分Maybe a。这样的一个a可能存在也可能不存在。如果Maybe a价值是Just x针对某些人的x :: a,那么该部分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
Run Code Online (Sandbox Code Playgroud)
该光学视图是一个整体State。第一个镜头stStep聚焦于其StateStep领域。如果这StateStep是一个StateRun x (Just y)值,则_StateRun棱镜聚焦在该(x, Just y)零件上,而_2镜头进一步聚焦在该Just y零件上,_Just棱镜进一步聚焦在该y :: Text零件上。
另一方面,如果StateStep视场是 a StatePause,则光学器件optic1不会聚焦在任何物体上(因为第二个分量棱镜_StateRun不会聚焦在任何物体上),如果是 a StateRun x Nothing,光学器件optic1 仍然不会聚焦在任何物体上,因为即使虽然_StateRun可以聚焦(x, Nothing)并且_2可以聚焦Nothing,但最终_Just没有聚焦于任何东西,因此整个光学器件无法聚焦。
特别是,在处理 a并尝试引用丢失的第二个字段或类似内容时,不存在镜头_2“失火”的危险。StatePause事实上,您过去常常_StateRun关注StateRun构造函数的字段元组,这确保了在整个光学器件聚焦时所需的字段将出现。
现在,这就是为什么你要使用第二个光学器件:
optic2 = stStep . _StateRun . _Just . stMMistake
Run Code Online (Sandbox Code Playgroud)
不起作用...
实际上有两个问题。第一,stStep . _StateRun注重整体State,注重局部(Int, Maybe Text)。这不是一个Maybe值,所以它还不能与_Just棱镜合成。您想Maybe Text先选择字段,然后应用_Just棱镜,所以您真正想要的更像是:
optic3 = stStep . _StateRun . stMMistake . _Just
Run Code Online (Sandbox Code Playgroud)
这看起来确实应该有效,对吧?镜头stStep聚焦在 a 上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
Run Code Online (Sandbox Code Playgroud)
这不是镜头或任何东西的一些基本理论特性。makeLenses这只是and使用的命名和类型约定makePrisms。使用makeLenses,您可以创建专注于数据结构的命名字段的光学器件。如果只有一个构造函数:
data Foo = Bar { _x :: Int, _y :: Double }
Run Code Online (Sandbox Code Playgroud)
或者如果有多个构造函数但该字段存在于所有构造函数中:
data Foo = Bar { _x :: Int, _y :: Double }
| Baz { _x :: Int, _z :: Char }
Run Code Online (Sandbox Code Playgroud)
那么场光学器件(x在本例中)就是始终聚焦于该场的透镜。如果有多个构造函数,有些有该字段,有些没有:
data Foo = Bar { _x :: Int, _y :: Double }
| Baz { _x :: Int, _z :: Char }
| Quux { _f :: Int -> Double }
Run Code Online (Sandbox Code Playgroud)
那么场光学器件(x此处)是聚焦于场的光学器件(遍历),但仅当它存在时(即,当值为 aBar或 a时Baz,但不是当它为 a 时Quux)。
另一方面,makePrisms始终创建将字段作为未命名元组关注的构造函数棱镜,并且需要使用 、 等引用这些字段_1,_2而不是使用这些字段在该构造函数中碰巧具有的任何名称。
也许这回答了你的问题?