2013-06-03 6 views
5

Lassen Sie uns ein Spiel spielen. Es gibt zwei Stapel, die wir verwenden werden, beide bestehen aus schwarzen/weißen Chips.Aktualisieren mehrerer Teilfelder eines Feldes mit Ekmett Lens

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

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

Ein wirklich kluger Schachzug wäre einen schwarzen Chip im Stapel A zu übergeben, und einen weißen Chip - in Haufen B. Aber wie?

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

Nicht sehr elegant. Wie kann ich es tun, ohne jeden Stapel zweimal zu referenzieren?

Das einzige, was ich mit kam (und Ich mag es nicht wichtig):

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

(leider im Voraus, wenn es offensichtlich ist - ich bin irgendwie neu zu Linsen und ich fühle mich im Meer verloren von Kombinatoren und Operatoren lens Angebote. es ist wahrscheinlich alles, was für Bedürfnisse der jeder dort versteckt. Nicht, dass es schlecht ist, natürlich ist! aber ich wünschte, es gehörte auch ein komplettes Handbuch war.)

+2

Was gefällt Ihnen an Option 2 nicht? Es kann nicht viel prägnanter als das sein, oder? – leftaroundabout

+0

@leftaroundabout Ich mag es nicht, dass ich Brackets benutzen musste, die zu plump werden, wenn mehrzeilige Ausdrücke involviert sind - wie Do-Blöcke und weitere Ebenen der Verschachtelung. – Artyom

+4

Ich denke, es würde helfen, wenn Sie einen groben Pseudocode zeigen würden, der Ihrer idealen Syntax entspricht. –

Antwort

5

A Traversal eine Verallgemeinerung von Lens ist, die „Brennpunkte "auf mehreren Werten. Denken Sie daran wie traverse, die Sie durch eine Traversable t Ändern von Werten in einem Applicative (traverse :: (Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b) sieht fast so ähnlich wie der Typ einer Lens bereits, Sie werden bemerken können - nur an t b ~ whole denken).

Für eine Traversal können wir nur die Werte auswählen, die wir ändern möchten. Zum Beispiel, lassen Sie mich Ihre Pile ein bisschen verallgemeinern und baute eine 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 

so wie man sehen kann, besuche ich sowohl die blacks und die whites mit f aber namepure verlassen. Dies ist fast die gleiche Art und Weise wie Sie eine Traversable Instanz schreiben, außer dass Sie immer alle der (homogenen) Elemente in der Struktur Traversable enthalten.

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

Dies ist nicht genug zu tun, was Sie wollen, obwohl, da Sie Ihre Felder in verschiedenen Möglichkeiten zu aktualisieren. Dafür müssen Sie Ihre Logik angeben und sicherstellen, dass sie ein völlig anderes Regelwerk enthält.

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