在 State monad 中有条件地更新地图的简洁方法

Concise way to conditionally update map in State monad

下面是来自 an answer regarding memoization 的代码,显示了状态 monad 中使用的记忆函数,如果键不在映射中,状态将使用传递的函数的结果进行更新。

type MyMemo a b = State (Map.Map a b) b

myMemo :: Ord a => (a -> MyMemo a b) -> a -> MyMemo a b
myMemo f x = do
  map <- get
  case Map.lookup x map of
    Just y  -> return y
    Nothing -> do
      y <- f x
      modify $ \map' -> Map.insert x y map'
      return y

它看起来不像惯用语Haskell:感觉非常必要,每行并没有那么多。

有没有办法以更 concise/functional 的方式执行上述操作?我查看了 http://hackage.haskell.org/package/transformers-0.5.4.0/docs/Control-Monad-Trans-State-Lazy.html#v:state 上可用的函数,但似乎没什么用。

我认为你的代码是函数式的,但你可以稍微简化一下。

myMemo f x = maybe work return =<< gets (Map.lookup x)
  where
    work = do
        y <- f x
        modify $ Map.insert x y
        return y

这是使用 mapState 以及来自 >>=maybe 的替代方案,它避免了所有的符号

myMemo f x = gets (Map.lookup x) >>= maybe y' return
  where
    y' = mapState (\(y, map) -> (y, Map.insert x y map)) $ f x 

这是在 基础上扩展的替代方案,使用更多 >>= 避免了所有 do 表示法

myMemo :: Ord a => (a -> MyMemo a b) -> a -> MyMemo a b
myMemo f x = gets (Map.lookup x) >>= maybe y' return
  where
    y' = f x >>= \y -> state $ \map -> (y, Map.insert x y map)

这是在 基础上扩展的替代方案,本质上是对 do-notation

进行去糖处理
myMemo f x = gets (Map.lookup x) >>= maybe y' return
  where
    y' = f x >>= \y -> modify (Map.insert x y) >> return y