haskell / haskell/containers

traverseWithKey for Data.Map looks too clever

Open
#229 0 comments 0 reactions 0 assignees View on GitHub
Map performance
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.