Haskell 使用ekmett’更新字段的多个子字段;s透镜
让我们玩一个游戏。我们将要使用两个堆,都由黑/白边芯片组成Haskell 使用ekmett’更新字段的多个子字段;s透镜,haskell,lenses,Haskell,Lenses,让我们玩一个游戏。我们将要使用两个堆,都由黑/白边芯片组成 data Pile = Pile { _blacks, _whites :: Int } makeLenses ''Pile data Game = Game { _pileA, _pileB :: Pile } makeLenses ''Game 一个非常聪明的做法是在A堆中翻转一个黑色的芯片,在B堆中翻转一个白色的芯片。但是如何翻转呢 cleverMove :: Game -> Game cleverMove game =
data Pile = Pile { _blacks, _whites :: Int }
makeLenses ''Pile
data Game = Game { _pileA, _pileB :: Pile }
makeLenses ''Game
一个非常聪明的做法是在A堆中翻转一个黑色的芯片,在B堆中翻转一个白色的芯片。但是如何翻转呢
cleverMove :: Game -> Game
cleverMove game = game & pileA . blacks -~ 1
& pileA . whites +~ 1
& pileB . blacks +~ 1
& pileB . whites -~ 1
不太优雅。我如何在不引用每一堆两次的情况下完成它
我想到的唯一一件事(我不喜欢):
(如果这是显而易见的,请提前道歉-我对镜头有点陌生,我感觉迷失在组合器和操作符的海洋中
lens
provides。那里可能藏着满足每个人需求的所有东西。当然,这并不坏!但我希望还包括一本完整的手册。)ATraversal
是Lens
的泛化,它“关注”多个值。可以将其想象为遍历
,它允许您单步通过可遍历的t
修改应用程序
中的值(遍历::(应用程序f,可遍历的t)=>(a->f b)->t a->f(t b)
看起来已经很像镜头的类型了,你会注意到只要想想tb~整个
)
对于遍历
,我们只需选择要更改的值即可。例如,让我概括一下您的堆
,并构建一个遍历
data Pile = Pile { _blacks :: Int, _whites :: Int, _name :: String } deriving (Show)
$(makeLenses ''Pile)
counts :: Traversal' Pile Int
counts f (Pile blacks whites name) =
Pile <$> f blacks <*> f whites <*> pure name
但是,这还不足以满足您的需要,因为您需要以不同的方式更新字段。为此,您需要指定您的逻辑,并确保它支持一套完全不同的规则
blackToWhite :: Pile -> Pile
blackToWhite = (blacks -~ 1) . (whites +~ 1)
您不喜欢选项2的哪些方面?没有比这更简洁的了,是吗?@leftaroundabout我不喜欢这样,我不得不用括号,当涉及到多行表达式时,这会变得很笨拙-例如do
块和更高级别的嵌套。我认为如果您显示一些与理想语法对应的粗略伪代码会有所帮助。@GabrielGonzalez我的理想语法与我的“非理想”语法相同,只是没有括号。问题是,我想知道惯用的解决方案,如果有(因为更新多个子字段应该是一项相当常见的任务,我会惊讶地知道没有)。如果单个更新开始占用多行,可能是时候退一步,启动let
或where
块,然后开始命名。
Main*> Pile 0 0 "test" & counts +~ 1
Pile {_blacks = 1, _whites = 1, _name = "test"}
blackToWhite :: Pile -> Pile
blackToWhite = (blacks -~ 1) . (whites +~ 1)