snoyberg / mono-traversable

Type classes for mapping, folding, and traversing monomorphic containers
152 stars 63 forks source link

mapWithKey does not allow the resulting map to be of different type #162

Open cies opened 6 years ago

cies commented 6 years ago

This errors out:

:set -XTypeFamilies
:set -XNoImplicitPrelude
:set -XFlexibleContexts
:set -XOverloadedStrings
:m ClassyPrelude
let m = mapFromList ([("a", 1), ("b", 2)] :: [(Text, Int)])
mapWithKey (\k v -> k ++ tshow v) m

And it seems obvious why:

:t mapWithKey

Outputs:

mapWithKey :: IsMap map =>
     (ContainerKey map -> MapValue map -> MapValue map) -> map -> map

So it can only map to exactly the same type of map. Very much what I'm not expecting from a map function in a higher-kinded language. Which would be:

map :: Functor f => (a -> b) -> f a -> f b

Allowing me (but obviously not forcing me) to change the type of map with mapping.

Is this restriction really needed? And, if yes, how do I get around it?

I'm now going the mapToList, processing, and then back with mapFromList, route.

I really like classy-prelude and all it brings me: thanks a lot for open source'ing and maintaining it!

snoyberg commented 6 years ago

I think the monomorphic nature of the typeclasses disallows a more general function to be written. I don't have any thoughts on how to do it, but if you or someone else can think of a way, I'm not opposed on principle.

On Wed, Apr 4, 2018 at 11:23 AM, Cies Breijs notifications@github.com wrote:

This errors out:

:set -XTypeFamilies :set -XNoImplicitPrelude :set -XFlexibleContexts :set -XOverloadedStrings :m ClassyPrelude let m = mapFromList ([("a", 1), ("b", 2)] :: [(Text, Int)]) mapWithKey (\k v -> k ++ tshow v) m

And it seems obvious why:

:t mapWithKey

Outputs:

mapWithKey :: IsMap map => (ContainerKey map -> MapValue map -> MapValue map) -> map -> map

So it can only map to exactly the same type of map. Very much what I'm not expecting from a map function in a higher-kinded language. Which would be:

map :: Functor f => (a -> b) -> f a -> f b

Allowing me (but obviously not forcing me) to change the type of map with mapping.

Is this restriction really needed? And, if yes, how do I get around it?

I'm now going the mapToList, processing, and then back with mapFromList, route.

I really like classy-prelude and all it brings me: thanks a lot for open source'ing and maintaining it!

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/snoyberg/mono-traversable/issues/162, or mute the thread https://github.com/notifications/unsubscribe-auth/AADBB81XsWGqkmDmVOAUmFwwro9UXm-Lks5tlIL0gaJpZM4TGXcb .

cies commented 6 years ago

Thanks for the apt response. If /you/ have no idea how to generalize this, then I give up right here. Maybe a small note in the docs that this does not behave as one might expect from a map function (it's more like an updateEach) as "the monomorphic nature of the typeclasses disallows a more general approach" is all I can think of.

Feel free to close, I'm fine with the mapToList-mapFromList approach.

snoyberg commented 6 years ago

A PR for that doc update would be greatly appreciated!

On Wed, Apr 4, 2018 at 12:06 PM, Cies Breijs notifications@github.com wrote:

Thanks for the apt response. If /you/ have no idea how to generalize this, then I give up right here. Maybe a small note in the docs that this does not behave as one might expect from a map function (it's more like an updateEach) as "the monomorphic nature of the typeclasses disallows a more general approach" is all I can think of.

Feel free to close, I'm fine with the mapToList-mapFromList approach.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/snoyberg/mono-traversable/issues/162#issuecomment-378532620, or mute the thread https://github.com/notifications/unsubscribe-auth/AADBB0kZ3bjHUhlBRSq8pDivsD2UAgZ1ks5tlI0IgaJpZM4TGXcb .

sniperrifle2004 commented 5 years ago

I don't know if it is an appropriate solution, but PolyMap (being polymorphic) could have such a function if it had an associated key type:

type PolyMapKey map

pmapWithKey ::  (PolyMapKey map -> v1 -> v2) -> map v1 -> map v2

Without it is also possible, but you would have to rely on SetContainer's ContainerKey:

pmapWithKey :: (SetContainer (map v1), SetContainer (map v2),
      (ContainerKey (map v1)) ~ (ContainerKey (map v2)))
      => (ContainerKey (map v1) -> v1 -> v2)
      -> map v1 -> map v2

Which seems a bit to restrictive to me, but it does work for all current instances of PolyMap.