traverseWithKey for Data.Map looks too clever
- Dominant language
- Haskell
- Stars
- 355
- Forks
- 194
- Avg merge
- 3d 4h
- Merged PRs (30d)
- 4
Description
We have
``` haskell
traverseWithKey :: Applicative t => (k -> a -> t b) -> Map k a -> t (Map k b)
traverseWithKey f = go
where
go Tip = pure Tip
go (Bin 1 k v _ _) = (\v' -> Bin 1 k v' Tip Tip) <$> f k v
go (Bin s k v l r) = flip (Bin s k) <$> go l <*> f k v <*> go r
{-# INLINE traverseWithKey #-}
```
This uses the annotation invariant to collapse all cases involving `Tip` into one. But is this really a good idea? I doubt it! Depending on the functor involved, using `<$>` or `<*>` with `pure Tip` may not be free. If we make the cases explicit, we can avoid those applications:
``` haskell
go Tip = pure Tip
go (Bin _ k v Tip Tip) = (\v' -> Bin 1 k v' Tip Tip) <$> f k v
go (Bin s k v Tip r) = (\v' r' -> Bin s k v' Tip r') <$> f k v <*> go r
go (Bin s k v l Tip) = (\l' v' -> Bin s k v' l' Tip) <$> go l <*> f k v
go (Bin s k v l r) = flip (Bin s k) <$> go l <*> f k v <*> go r
```
Another option that may reduce code size but that I suspect may pre-allocate `pure Tip` as the current implementation does:
``` haskell
go Tip = pure Tip
go (Bin s k v l r)
| s <= 3 = case l of ...
case r of ...
| otherwise = flip (Bin s k) <$> go l <*> f k v <*> go r
```
We should also do some benchmarking to see if this `go` function should take an `f` argument, whether it should have a type signature, etc.
Contributor guide
Assessment
This issue has not been assessed yet.