Как использовать объективы для поиска значения на карте, увеличения или установки значения по умолчанию

Во время работы над состоянием AppState я хочу отслеживать количество экземпляров, скажем,. Эти экземпляры имеют различные идентификаторы типа InstanceId.

Поэтому мой взгляд на состояние нравится этому

import Control.Lens
data AppState = AppState
 { -- ...
 , _instanceCounter :: Map InstanceId Integer
 }
makeLenses ''AppState

Функция отслеживания счетчиков должна давать 1, если ни один экземпляр с данным идентификатором не был подсчитан до и n + 1 иначе:

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

В то время как приведенный выше код работает, есть, надеюсь, способ его улучшить. Что мне не нравится:

  • Мне нужно дважды отобразить карту instanceCounter (сначала для установки, затем для получения значения)
  • Я использую fromMaybe, где всегда ожидается Just (поэтому я мог бы также использовать fromJust)
  • Я не использую объективы для поиска и вставки в incOrSetToOne. Причина в том, что at не позволяет обрабатывать случай, когда lookup дает Nothing, но вместо этого fmap над Maybe.

Предложения по улучшению?

3 ответа

Способ использования этого объектива:

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

Ключевым моментом здесь является использование non

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

Это позволяет нам обрабатывать отсутствующие элементы с карты экземпляра в качестве 0


Один из способов - использовать оператор <%=. Это позволяет вам изменять цель и возвращать результат:

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

который имеет "частичный" шаблон, который должен логически совпадать.

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


Я бы использовал

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

или

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

Я не знаю, есть ли объективный способ сделать то же самое.

licensed under cc by-sa 3.0 with attribution.