dictionaryhaskell

How to convert Map.adjust to monadic?


In the code it works well as below

divide :: Int -> Int -> Either String Int
divide a 0 = Left "Error with 0"
divide a b = Right (a `div` b)

The code to be changed

Map.adjust (divide 3) "A" $ Map.fromList [("A",3),("B",0)]

The expected result should be :

Map.adjust (divide 3) "A" $ Map.fromList [("A",3),("B",0)] 
=>  Right $ Map.fromList [("A",1),("B",0)]   

Map.adjust (divide 3) "B" $ Map.fromList [("A",3),("B",0)]
=>  Left "Error with 0"

Or in general how to build a function like:

Map.adjust:: (a -> m a) -> k -> (Map k a) -> m (Map k a)

Thank you very much !


Solution

  • A function adjustM with type (a -> m a) -> k -> (Map k a) -> m (Map k a) can be defined on top of the already existing function alterF :: (Functor f, Ord k) => (Maybe a -> f (Maybe a)) -> k -> Map k a -> f (Map k a).

    alterF is more general because, besides allowing for effects, it also lets you remove the targeted value, or insert it when the key doesn't already exist. (That's what those Maybes are for in its signature).

    adjustM :: (Ord k, Applicative m) => (a -> m a) -> k -> Map k a -> m (Map k a)
    adjustM f = Map.alterF (\ma -> case ma of
       -- No value? We do nothing.
       Nothing -> pure Nothing
       -- There's a value? We transform it.
       Just v -> Just <$> f v)
    

    Or, more succinctly,

    adjustM :: (Ord k, Applicative m) => (a -> m a) -> k -> Map k a -> m (Map k a)
    adjustM f = Map.alterF (traverse f)