Add Map curry/uncurry utils - #1239
Conversation
|
|
||
| prop_curryMap :: Map Int (Map Int A) -> Property | ||
| prop_curryMap m = m' === uncurryMap (curryMap m') | ||
| where m' = uncurryMap m |
There was a problem hiding this comment.
I wanted to simply have
prop_curryMap :: Map (Int, Int) A -> Property
prop_curryMap m = m === curryMap (uncurryMap m)
but that yields • No instance for ‘IsInt (Int, Int)’ for some reason.
| -- | ||
| -- @since FIXME | ||
| curryMap :: (Ord a, Ord b) => Map (a,b) c -> Map a (Map b c) | ||
| curryMap m = fmap (fromDescList . ($ [])) $ fromAscListWith (.) $ fmap (\((a,b),c) -> (a, ((b,c):))) $ toAscList m |
There was a problem hiding this comment.
Surprisingly to me this consistently comes up slightly faster and with significantly lower allocations
curryMap: OK
262 μs ± 15 μs, 2.3 MB allocated, 116 KB copied, 23 MB peak memory
than
curryMap: OK
273 μs ± 13 μs, 2.4 MB allocated, 142 KB copied, 23 MB peak memory
curryMap m = fmap M.fromDescList $ M.fromAscListWith (<>) $ fmap (\((a,b),c) -> (a, [(b,c)])) $ M.toAscList m
on cabal bench map-benchmarks --benchmark-options "-p curryMap +RTS -T".
I guess it's because we skip the pattern match on singleton lists, but I expected the overhead of a closure to be worse. I guess GHC gets to optimize this case a lot more.
| curryMap :: (Ord a, Ord b) => Map (a,b) c -> Map a (Map b c) | ||
| curryMap m = fmap (fromDescList . ($ [])) $ fromAscListWith (.) $ fmap (\((a,b),c) -> (a, ((b,c):))) $ toAscList m |
There was a problem hiding this comment.
| curryMap :: (Ord a, Ord b) => Map (a,b) c -> Map a (Map b c) | |
| curryMap m = fmap (fromDescList . ($ [])) $ fromAscListWith (.) $ fmap (\((a,b),c) -> (a, ((b,c):))) $ toAscList m | |
| curry :: (Ord a, Ord b) => Map (a,b) c -> Map a (Map b c) | |
| curry m = fmap (fromDescList . ($ [])) $ fromAscListWith (.) $ fmap (\((a,b),c) -> (a, ((b,c):))) $ toAscList m |
I think just this because the intended use-case would be to import qualified (Data.Map.curry) and use that for disambiguation.
There was a problem hiding this comment.
Agreed, I forgot to change the names when upstreaming
|
This is interesting to consider for sure, but I am not not seeing the utility. In what situation do you use it in your project? |
I am given a I added IIRC @Ericson2314 also had a FRP use-case? |
|
How do you have a Consider that somebody wants data Foo = Foo !Bar !Baz deriving Ord
curryFooMap :: Map Foo a -> Map Bar (Map Baz a)
curryFooMap = ??but they are unable to use |
|
It goes something like this
(1) is done by streaming lines from a file (with
Btw, as of #1230, (2) can be done with
I mean, it is polymorphic in |
|
Thanks, that sheds some light on it. I think it also echoes my point about it being odd to have foo :: (ByteString -> Maybe (Key1, Key2)) -> Map ByteString Word -> Map Key1 (Map Key2 Word)and I'm trying to think if there is a nice way to offer this functionality... By the way, there is a third implementation you could try using
Yes I do feel the same way about |
Ooooh nice I've wanted exactly this for something else but couldn't find it. I've been able to exploit some structure of my input to go straight to nested maps which then I accumulate with IntMap.fromListWith (Map.unionWith (+))but the IntMap.fromListUpsert (\(e,c) -> maybe (Map.singleton e c) (Map.insertWith (+) e c))made for 7% speedup on the merging part. Seems to make no difference for
Well, the venerable
It's not strictly needed in my use-case, but having a product/record type as
Tricky. At first glance one might think that offering the small pieces covers every use-case, e.g. foo :: (a -> Maybe (a1, a2)) -> Map a b -> Map a1 (Map a2 b)
foo f = curryMap . mapKeys fbut notice for module Data.IntMap
curry :: Map (Int,a) b -> IntMap (Map a b)
uncurry :: IntMap (Map a b) -> Map (Int,a) bThis pattern extends far beyond |
|
That last point made me notice there's other possible combinations, since curry :: Set (a,b) -> Map a (Set b)
uncurry :: Map a (Set b) -> Set (a,b)curry :: Set (Int,b) -> IntMap (Set b)
uncurry :: IntMap (Set b) -> Set (Int,b) |
I've been using a
curryMapon a project of mine and realised at some point it could be madeO(n).Given it's non-trivial how to do it right, upstreaming seems like it makes sense.