2015-09-02 13 views
6

Mentre si lavora su uno stato chiamato AppState Voglio tenere traccia del numero di, per esempio, delle istanze. Queste istanze hanno ID distinti di tipo InstanceId.Come utilizzare gli obiettivi per cercare un valore in una mappa, aumentarlo o impostarlo su un valore predefinito

Perciò il mio sguardo stato piace questo

import   Control.Lens 

data AppState = AppState 
    { -- ... 
    , _instanceCounter :: Map InstanceId Integer 
    } 

makeLenses ''AppState 

La funzione per tenere traccia dei conteggi dovrebbe produrre 1 quando alcuna istanza con dato id è stato contato prima e n + 1 altrimenti:

import Data.Map as Map 
import Data.Map (Map) 

countInstances :: InstanceId -> State AppState Integer 
countInstances instanceId = do 
    instanceCounter %= incOrSetToOne 
    fromMaybe (error "This cannot logically happen.") 
       <$> use (instanceCounter . at instanceId) 
    where 
    incOrSetToOne :: Map InstanceId Integer -> Map InstanceId Integer 
    incOrSetToOne m = case Map.lookup instanceId m of 
     Just c -> Map.insert instanceId (c + 1) m 
     Nothing -> Map.insert instanceId 1 m 

Mentre il il codice sopra funziona, c'è sperabilmente un modo per migliorarlo. Quello che non mi piace:

  • devo evocare la mappa instanceCounter due volte (prima per l'impostazione, quindi per ottenere il valore)
  • Io uso fromMaybe dove sempre Just è previsto (quindi tanto vale usare fromJust)
  • Non uso gli obiettivi per la ricerca e l'inserimento in incOrSetToOne. Il motivo è che at non consente di gestire il caso in cui lookup produce Nothing ma invece fmap s su Maybe.

Suggerimenti per miglioramenti?

risposta

7

Il modo per farlo questo obiettivo usando è:

countInstances :: InstanceId -> State AppState Integer 
countInstances instanceId = instanceCounter . at instanceId . non 0 <+= 1 

La chiave qui è quello di utilizzare non

non :: Eq a => a -> Iso' (Maybe a) a 

Questo ci permette di trattare gli elementi mancanti dalla instanceCounter mappa come 0

1

userei

incOrSetToOne = Map.alter (Just . maybe 1 succ) instanceId 

o

incOrSetToOne = Map.alter ((<|> Just 1) . fmap succ) instanceId 

Non so se c'è un modo lensy a fare lo stesso.

3

Un modo è utilizzare l'operatore <%=. Esso consente di modificare il target e restituire il risultato:

import Control.Lens 
import qualified Data.Map as M 
import Data.Map (Map) 
import Control.Monad.State 

type InstanceId = Int 

data AppState = AppState { _instanceCounter :: Map InstanceId Integer } 
    deriving Show 

makeLenses ''AppState 

countInstances :: InstanceId -> State AppState Integer 
countInstances instanceId = do 
    Just i <- instanceCounter . at instanceId <%= Just . maybe 1 (+1) 
    return i 

initialState :: AppState 
initialState = AppState $ M.fromList [(1, 100), (3, 200)] 

che ha un modello "parziale" che dovrebbe corrispondere sempre in modo logico.

> runState (countInstances 1) initialState 
(101,AppState {_instanceCounter = fromList [(1,101),(3,200)]}) 
> runState (countInstances 2) initialState 
(1,AppState {_instanceCounter = fromList [(1,100),(2,1),(3,200)]}) 
> runState (countInstances 300) initialState 
(201,AppState {_instanceCounter = fromList [(1,100),(3,201)]}) 
+0

Mi sento come se non avessi capito fino in fondo ... ma ... ora il "Just ... Just' sembra ridondante. Devo sperimentare ancora, ma è proprio quello che stavo cercando. –

+0

OK, quindi la chiave per me era capire la funzione [alter] (http://hackage.haskell.org/package/containers-0.5.6.3/docs/Data-Map-Strict.html#v:alter) che ha nella sua firma una funzione 'Maybe a -> Maybe a' per impostare o annullare i valori delle mappe. –

+0

E in termini di miglioramento, le risposte di glguy superano le tue. Scusate! –