代码之家  ›  专栏  ›  技术社区  ›  ruben.moor

如何使用镜头在地图中查找值、增加值或将其设置为默认值

  •  8
  • ruben.moor  · 技术社区  · 9 年前

    在处理名为 AppState 我想跟踪实例的数量。这些实例具有不同的类型ID InstanceId .

    所以我的状态是这样的

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

    如果之前没有对具有给定id的实例进行计数,则跟踪计数的函数应为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 s结束 Maybe .

    改进建议?

    3 回复  |  直到 9 年前
        1
  •  9
  •   glguy    9 年前

    使用镜头的方法是:

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

    这里的关键是使用 non

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

    这允许我们将instanceCounter Map中缺少的元素视为0

        2
  •  5
  •   cchalmers    9 年前

    一种方法是使用 <%= 操作人员它允许您更改目标并返回结果:

    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)]})
    
        3
  •  1
  •   chi    9 年前

    我会用

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

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

    我不知道是否有一种巧妙的方法可以做到这一点。