2 ms·
Sure. Let's look at alterF for the Map type in the containers library: http://hackage.haskell.org/package/containers-0.6.2.1/docs/Data-Map-Strict.html#v:alterF
by chowells 7y ago
Sure. Let's look at alterF for the Map type in the containers library:
http://hackage.haskell.org/package/containers-0.6.2.1/docs/Data-Map-Strict.html#v:alterF http://hackage.haskell.org/package/containers-0.6.2.1/docs/D...
It has the type:
alterF :: (Functor f, Ord k) => (Maybe a -> f (Maybe a)) -> k -> Map k a -> f (Map k a)
Note that it works for all instances of Functor. Trivial choices allow this to reduce to simple things like insert or lookup:
insert :: Ord k => k -> a -> Map k a -> Map k a
insert key val map = runIdentity (alterF (\_ -> Identity (Just val)) key map)
lookup :: Ord k => k -> Map k a -> Maybe a
lookup key map = getConst (alterF Const key map)
But those aren't really compelling examples because they just reproduce simpler functionality. It starts to pay off when you start compounding requirements, though. What if you need to insert a value and return the previous one, if it existed?
insertAndReturnOld :: Ord k => k -> a -> Map k a -> (Maybe c, Map k a)
insertAndReturnOld key val map = alterF (\old -> (old, Just val)) key map
OK, those are all fine and good, but they're still barely scratching the surface. What if you had problem where when you had a value to insert and there was a previous value at the same key, it was ambiguous which you should use? And let's say the structure of the problem provides interdepencies which restrict the options such that you can't just stack up a list of every possibility for each key. So ideally, you'd like a way to model "insert this value, but if something was already present at this key, give me both possible maps back". Turns out alterF can do that!
insertNonDet :: Ord k => k -> a -> Map k a -> [Map k a]
insertNonDet key val map = alterF (maybe [Just val] (\old -> [Just old, Just Val])) key map
Fun facts with that one - thanks to using Functor, it only traverses the tree once, whether it returns one or two results. And thanks to Map being a persistent data type, returning two results only takes O(log n) space more than returning one.
(note: this is all typed on my phone without a compiler to verify. There might be simple mistakes in the above, but it's all conceptually sound.)
That's all still just the start. You can insert an IO operation on the old value to calculate the new one, and it still only traverses the data structure once. Or a huge number of other things. Anything that can be made into a Functor can be used to augment the operation alterF does.
And you know the best part of all this? Despite all that freedom, the type of alterF tells you that you can't change the key associated with a value and even that you can't operate on more than one value in the map. It really is nice when simple things give you lots of options, but make it clear what they don't offer.