2015-09-02 5 views
6

Podczas pracy nad stanem o nazwie AppState chcę śledzić liczbę instancji, powiedzmy. Te wystąpienia mają różne identyfikatory typu: InstanceId.Jak używać soczewek do wyszukiwania wartości na mapie, zwiększania jej lub ustawiania na wartość domyślną

Dlatego mój wygląd stan lubi to

import   Control.Lens 

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

makeLenses ''AppState 

funkcji, aby śledzić liczy powinna przynieść 1, gdy nie ma instancji z podanym identyfikatorze została liczone przed i n + 1 inaczej:

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 

ile powyższy kod działa, jest nadzieja, że ​​uda się go poprawić. Co mi się nie podoba:

  • muszę wywołać mapę instanceCounter dwukrotnie (pierwszy do ustawiania, a następnie na uzyskanie wartości)
  • używam fromMaybe gdzie zawsze Just oczekuje się (a więc równie dobrze mogę używać fromJust)
  • Nie używam soczewek do wyszukiwania i wstawiania w incOrSetToOne. Powodem jest to, że at nie pozwala obsłużyć przypadku, w którym lookup daje Nothing, ale zamiast tego s nad Maybe.

Sugestie do poprawy?

Odpowiedz

7

Sposób to zrobić przy użyciu obiektywu wynosi:

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

Kluczem tutaj jest użycie non

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

To pozwala nam traktować brakujące elementy z instanceCounter map jako 0

1

użyłbym

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

lub

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

Nie wiem, czy istnieje lensy sposób zrobić to samo.

3

Jednym ze sposobów jest użycie operatora <%=. Umożliwia zmianę celu i zwrócenie wyniku:

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

, który ma wzór "częściowy", który powinien zawsze być zgodny z logiką.

> 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

Czuję, że jeszcze nie w pełni rozumiałem "w" ... teraz "Po prostu ... Po prostu" wygląda na zbędny. Muszę jeszcze trochę eksperymentować, ale tego właśnie szukałem. –

+0

OK, więc kluczem dla mnie było zrozumienie funkcji [alter] (http://hackage.haskell.org/package/containers-0.5.6.3/docs/Data-Map-Strict.html#v:alter), która ma w swoim podpisie funkcję 'Może a -> Może a' ustawić lub anulować wartości map. –

+0

I jeśli chodzi o poprawę, odpowiedzi glguy są na topie. Przepraszam! –

Powiązane problemy