2013-06-03 3 views
5

Давайте сыграем в игру. Есть две сваи, которые мы собираемся использовать, состоящие из черных/белых односторонних чипов.Обновление нескольких подполей поля с использованием объектива ekmett's

data Pile = Pile { _blacks, _whites :: Int } 
makeLenses ''Pile 

data Game = Game { _pileA, _pileB :: Pile } 
makeLenses ''Game 

Действительно умный ход будет перевернуть черную фишку в свайном А, а белый чип - в свайном 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

Traversal является обобщением Lens, который «фокусирует "по нескольким значениям. Подумайте об этом, как traverse, который позволяет вам изменять значения Traversable t в Applicative (traverse :: (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 

так, как вы можете видеть, я посещаю как blacks и whites с f но оставить 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)