7

来玩个游戏。我们将使用两堆,都由黑/白边的芯片组成。

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

不是很优雅。如果不引用每个桩两次,我怎么能做到这一点?

我想出的唯一一件事(我不喜欢它):

cleverMove game = game & pileA %~ (blacks -~ 1)
                                . (whites +~ 1)
                       & pileB %~ (blacks +~ 1)
                                . (whites -~ 1)

(如果很明显,请提前抱歉 - 我对镜头有点陌生,我在组合器和操作员lens的报价中迷失了方向。可能每个人都需要的东西都藏在那里。当然不是说这很糟糕!但我希望有还包括完整的手册。)

4

1 回答 1

10

ATraversalLens“关注”多个值的概括。把它想象成它traverse允许你逐步Traversable t修改 an 中的值Applicativetraverse :: (Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b)看起来很像 a 的类型Lens,你会注意到——想想t b ~ whole)。

对于 aTraversal我们可以选择我们想要更改的值。例如,让我概括一下您Pile并构建一个Traversal.

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

如您所见,我同时访问了 theblacks和 the whiteswith fbut leave name pure。这几乎与您编写Traversable实例的方式相同,只是您总是访问结构中包含的所有(同质)元素Traversable

Main*> Pile 0 0 "test" & counts +~ 1
Pile {_blacks = 1, _whites = 1, _name = "test"}

但是,这还不足以满足您的需求,因为您需要以不同的方式更新您的字段。为此,您需要指定您的逻辑并确保它支持一组完全不同的规则。

blackToWhite :: Pile -> Pile
blackToWhite = (blacks -~ 1) . (whites +~ 1)
于 2013-06-03T14:35:54.780 回答