2013-06-03 60 views
5

讓我們玩一個遊戲。有兩堆我們要使用,包括黑色/白色雙面芯片。使用ekmett的鏡頭更新字段的多個子字段

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優惠。有可能是一切爲了大家的需求藏匿在那裏。不,這是不好的,當然!但我希望也有一個完整的手冊包括在內。)

+2

你不喜歡選項2?它不能比這更簡潔,可以嗎? – leftaroundabout

+0

@leftaroundabout我不喜歡我不得不使用括號,當涉及到多行表達式時會變得笨拙 - 例如'do'塊和更高級別的嵌套。 – Artyom

+4

我認爲如果你顯示一些粗略的僞代碼對應於你的理想語法,它會有所幫助。 –

回答

5

一個TraversalLens其中「重點泛化「關於多重價值。想想它就像traverse,它允許你通過Traversable t修改Applicativetraverse :: (Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b)看起來非常像Lens已經,你會注意到 - 只是想到t b ~ whole)的值。

對於Traversal,我們可以選取我們想要更改的值。例如,讓我簡單介紹一下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 

所以你可以看到,我參觀了兩個和blackswhitesf,但留下namepure。這與您編寫Traversable實例的方式幾乎相同,只是您總是訪問Traversable結構中包含的(同質)元素的全部

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

這是不夠的,做你想做的,不過,因爲你需要在不同方式更新您的領域。爲此,您需要指定您的邏輯並確保它遵循完全不同的一套規則。

blackToWhite :: Pile -> Pile 
blackToWhite = (blacks -~ 1) . (whites +~ 1)