2015-09-02 21 views
6

Während ich an einem Zustand namens AppState arbeite, möchte ich die Anzahl der Instanzen beobachten. Diese Instanzen haben unterschiedliche IDs des Typs InstanceId.So verwenden Sie Objektive, um einen Wert in einer Karte nachzuschlagen, zu erhöhen oder auf einen Standardwert zu setzen

Deshalb mein Zustand Blick mag diese

import   Control.Lens 

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

makeLenses ''AppState 

Die Funktion Spur von Zählungen zu halten, sollte 1 ergeben, wenn keine Instanz mit angegebener ID wird vor und n + 1 anders gezählt:

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 

Während die oben Code funktioniert, gibt es hoffentlich eine Möglichkeit, es zu verbessern. Was ich nicht mag:

  • I instanceCounter zweimal die Karte evozieren haben (zuerst für die Einstellung, dann wird der Wert für das Erhalten)
  • ich fromMaybe verwenden, wo immer Just zu erwarten ist (so könnte ich auch gebrauchen)
  • Ich verwende keine Linsen für das Nachschlagen und Einfügen in . Der Grund dafür ist, dass at nicht den Fall behandeln kann, in dem lookupNothing ergibt, sondern stattdessen fmap s über Maybe.

Vorschläge für Verbesserungen?

Antwort

7

Die Art und Weise, dies mit Objektiv zu tun ist:

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

Der Schlüssel hier ist non

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

Dies ermöglicht es uns, von der instanceCounter Karte fehlende Elemente als 0 zu behandeln, zu verwenden,

1

würde ich

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

oder

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

Ich weiß nicht verwenden, wenn es ein lensy Weg, um das gleiche zu tun.

3

Eine Möglichkeit besteht darin, den Operator <%= zu verwenden. Es ermöglicht Ihnen, das Ziel zu ändern und geben das Ergebnis:

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)] 

, die ein „teilweise“ Muster hat, die logisch immer übereinstimmen soll.

> 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

Ich habe das Gefühl, dass ich 'at' noch nicht ganz verstanden habe ... jetzt sieht das' Just ... Just' überflüssig aus. Ich muss etwas mehr experimentieren, aber das ist tatsächlich das, wonach ich gesucht habe. –

+0

OK, also war der Schlüssel für mich, die Funktion [alter] (http://hackage.haskell.org/package/containers-0.5.6.3/docs/Data-Map-Strict.html#v:alter) zu verstehen hat in seiner Signatur eine Funktion 'Maybe a -> Maybe a' um Kartenwerte zu setzen oder zu löschen. –

+0

Und in Bezug auf die Verbesserung, die Antworten von Glguy übertrifft Ihre. Es tut uns leid! –