如何实现单子式的Map.adjust函数?Haskell技术问询
解决方案
背景
现有可运行的Haskell除法函数:
divide :: Int -> Int -> Either String Int divide a 0 = Left "Error with 0" divide a b = Right (a `div` b)
我们需要改造调用逻辑,让调整Map值的操作支持错误反馈:
-- 原调用(仅纯函数逻辑,无法返回错误) Map.adjust (divide 3) "A" $ Map.fromList [("A",3),("B",0)]
期望实现以下效果:
- 当调整合法键(如"A")时,返回
Right包裹的更新后Map:adjustM (divide 3) "A" $ Map.fromList [("A",3),("B",0)] => Right $ Map.fromList [("A",1),("B",0)] - 当调整触发错误的键(如"B")时,返回
Left错误信息:adjustM (divide 3) "B" $ Map.fromList [("A",3),("B",0)] => Left "Error with 0"
本质是要实现一个支持monadic操作的通用调整函数,类型签名如下:
adjustM :: (Monad m) => (a -> m a) -> k -> Map k a -> m (Map k a)
实现代码
标准库的Data.Map.adjust是纯函数,不支持monadic上下文,我们可以自己实现这个增强版本:
import qualified Data.Map as Map adjustM :: (Monad m) => (a -> m a) -> Map.Key -> Map.Map Map.Key a -> m (Map.Map Map.Key a) adjustM f key map = case Map.lookup key map of -- 键不存在时,直接返回原Map的monadic包装 Nothing -> return map -- 键存在时,先执行monadic操作更新值,再插入原Map Just val -> do newVal <- f val return $ Map.insert key newVal map
测试验证
用divide函数测试两种场景:
- 合法调整场景:
adjustM (divide 3) "A" $ Map.fromList [("A",3),("B",0)] -- 输出:Right (fromList [("A",1),("B",0)]) - 错误触发场景:
adjustM (divide 3) "B" $ Map.fromList [("A",3),("B",0)] -- 输出:Left "Error with 0"
说明
adjustM是通用函数,支持任意Monad实例(不限于Either String)。核心逻辑是:先查找目标键,存在则执行传入的monadic函数更新值,不存在则保持原Map不变,整个过程都包裹在Monad上下文中,自然传递成功/失败状态。
内容的提问来源于stack exchange,提问作者Shawn Zhang
相关产品推荐
相关产品推荐

