2013-06-03 6 views
5

Zagrajmy w grę. Są dwa stosy, które zamierzamy użyć, oba składające się z czarnych/białych jednostronnych żetonów.Aktualizowanie wielu subpól pola za pomocą soczewki ekmett's

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

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

Naprawdę sprytnym posunięciem byłoby obrócenie czarnego żetonu w stosie A i białego żetonu - w stosie B. Ale jak?

cleverMove :: Game -> Game 
cleverMove game = game & pileA . blacks -~ 1 
         & pileA . whites +~ 1 
         & pileB . blacks +~ 1 
         & pileB . whites -~ 1 

Niezbyt elegancki. Jak mogę to zrobić bez dwukrotnego odniesienia do każdego stosu?

Jedyne co wymyśliłem (a ja się nie podoba):

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

(Przepraszam z góry jeżeli jest to oczywiste - Jestem trochę nowych do soczewek i czuję się zagubiony w morzu kombinatorów i operatorów lens oferty. Nie ma chyba wszystko na potrzeby każdego z nas ukrywa tam. Nie dlatego, że to jest złe, oczywiście! ale szkoda, że ​​nie było również kompletny podręcznik włączone.)

+2

Czego nie lubisz w opcji 2? Nie może być bardziej zwięzła, prawda? – leftaroundabout

+0

@leftaroundabout Nie podoba mi się to, że musiałem użyć nawiasów, które stają się niezdarne, gdy występują wyrażenia wielowierszowe - takie jak '' 'bloki i dalsze poziomy zagnieżdżania. – Artyom

+4

Myślę, że to pomogłoby, gdybyś pokazał jakiś szorstki pseudo-kod odpowiadający idealnej składni. –

Odpowiedz

5

Traversal jest uogólnieniem Lens które „skupia "na wielu wartościach. Potraktujcie to jak traverse który pozwala kroku przez Traversable t wartości modyfikujących w Applicative (traverse :: (Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b) wygląda dość podobnie do rodzaju progach Lens, zauważysz, tylko myśleć o t b ~ whole).

Dla Traversal możemy po prostu wybrać wartości, które chcemy zmienić. Na przykład, pozwól mi nieco uogólnić twoje Pile i zbudować 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 

więc jak widzisz, ja odwiedzić zarówno blacks i whites z f ale pozostawić namepure. Jest to prawie taki sam sposób, w jaki piszesz instancję Traversable, z tą różnicą, że zawsze odwiedzasz wszystkie (homogenicznych) elementów zawartych w strukturze Traversable.

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

to nie wystarczy, aby zrobić to, co chcesz, chociaż, ponieważ trzeba zaktualizować pola w różnych sposobów. W tym celu musisz określić swoją logikę i upewnić się, że zachowuje całkowicie inny zestaw zasad.

blackToWhite :: Pile -> Pile 
blackToWhite = (blacks -~ 1) . (whites +~ 1) 
Powiązane problemy