2013-06-03 4 views
5

Facciamo un gioco. Ci sono due pile che useremo, entrambe costituite da chip con lati neri/bianchi.Aggiornamento di più sottocampi di un campo usando l'obiettivo di ekmett

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

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

Davvero un mossa intelligente sarebbe quella di girare un chip nero in pile A, e un chip bianco - in pile B. Ma come?

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

Non molto elegante. Come posso farlo senza fare riferimento a ciascuna pila due volte?

L'unica cosa che mi è venuta (e non mi piace):

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

(Spiacente in anticipo se è ovvio - Sono un po 'nuova per lenti e mi sento perso in mare di combinatori e operatori lens offerte. c'è probabilmente tutto per è mai stato così nascosto lì. Non che sia male, naturalmente! ma mi piacerebbe che ci fosse anche un manuale completo incluso.)

+2

Cosa non ti piace dell'opzione 2? Non può essere molto più conciso di così, vero? – leftaroundabout

+0

@leftaroundabout Non mi piace che ho dovuto usare parentesi, che diventano maldestre quando sono coinvolte espressioni multilinea - come ad esempio blocchi 'do' e ulteriori livelli di nidificazione. – Artyom

+4

Penso che sarebbe d'aiuto se mostrassi qualche pseudo-codice approssimativo corrispondente a come sarebbe la tua sintassi ideale. –

risposta

5

un Traversal è una generalizzazione di Lens che "si concentra "su più valori. Pensa allo traverse che ti consente di scorrere i valori di Traversable t in uno Applicative (traverse :: (Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b) sembra già il tipo di uno Lens, noterai, basti pensare a t b ~ whole).

Per un Traversal possiamo semplicemente selezionare i valori che vogliamo modificare. Ad esempio, mi permetta di generalizzare un po 'il tuo Pile e di creare un 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 

così come si può vedere, ho visitare sia la blacks e la whites con f ma lasciare namepure. Questo è quasi lo stesso modo in cui si scrive un'istanza Traversable eccetto che si visita sempre tutti gli elementi degli elementi (omogenei) contenuti nella struttura Traversable.

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

Questo non è sufficiente per fare quello che vuoi, anche se, in quanto è necessario per aggiornare i campi in diversi modi. Per questo, è necessario specificare la logica e assicurarsi che rispetti un insieme di regole completamente diverso.

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