[GHC] #1460: Problem with Monoid instance of Data.Map
GHC
ghc-devs at haskell.org
Fri Aug 25 11:51:40 UTC 2017
#1460: Problem with Monoid instance of Data.Map
-------------------------------------+-------------------------------------
Reporter: ahey@… | Owner: (none)
Type: proposal | Status: closed
Priority: normal | Milestone: Not GHC
Component: libraries/base | Version: 6.6.1
Resolution: fixed | Keywords: Data.Map
| Monoid
Operating System: Unknown/Multiple | Architecture:
| Unknown/Multiple
Type of failure: None/Unknown | Test Case:
Blocked By: | Blocking:
Related Tickets: | Differential Rev(s):
Wiki Page: |
-------------------------------------+-------------------------------------
Comment (by andrewthad):
What I'm trying to argue is that the two possible `Monoid` instances for
`Map` aren't really equal in terms of how much they buy you. Let's look at
merging nested `Map`s again. The question here is "how much code do I have
to write to merge these?" Both for a deep merge and for a shallow merge.
{{{
type M = Map Int (Map Char (Map PersonId (Map ItemId (Set Foo))))
-- Assuming the current Monoid instance of Map
deep,shallow :: M -> M -> M
deep = unionWith (unionWith (unionWith (unionWith (<>))))
shallow = (<>)
-- Assuming the value-combining Monoid instance of Map
deep,shallow :: M -> M -> M
deep = (<>)
shallow = union
}}}
My point is that the value-combining instance helps us more than the left-
biased instance. Since the instances chain together, it's just one type
we're getting a better `Monoid` instance for. It's any arbitrary nesting
of them.
Let's look at a situation where the left-biased instance fares a little
better. What about the situations where you want a left-biased merge at
the bottom of a nesting of `Monoid`s? So, not a nesting of `Map`s, but a
nesting of other `Monoid`s with a `Map` at the bottom:
{{{
type K = Int -> Char -> IO ([ParseError],Map PersonId Person)
}}}
The left-biased instance has the desirable behavior here. If, after have
concatenated a bunch of expressions of type `K`, we try to parse two
people with the same id, we silently discard the second one. But, we can
easily recover this same behavior with the value-combining `Monoid`
instance with:
{{{
foo :: Int -> Char -> IO ([ParseError],Map PersonId (First Person))
}}}
If you don't actually want that `First` newtype in your final result, it
can be `coerce`d away safely as a noop. So, since the value-combining
instance can express the left-biased instance, even in the worst cases,
the semantics of the left-biased instance can be recovered by clever use
of `coerce`. However, the reverse is not true. The left-biased instance
cannot express the value-combining union, so currently when we want this,
we have to step outside the realm of `Monoid` instances entirely and
manually do the lifting. Just to emphasize the asymmetry in the amount and
kind of code the end user must write:
{{{
type J = Bar -> Baz -> IO ([Log], Map Pokemon (Set Attack))
type V = Bar -> Baz -> IO ([Log], Map Identifier Name)
leftBiased :: V -> V -> V
valueMerging :: J -> J -> J
-- with the left-biased Monoid instance
leftBiased = (<>)
valueMerging v1 v2 a b = liftA2 (\(x1,y1) (x2,y2) -> (x1 <> x2, unionWith
(<>) y1 y2)) (v1 a b) (v2 a b)
-- with the value-merging Monoid instance
type V' = Bar -> Baz -> IO ([Log], Map Identifier (First Name))
leftBiased a b = coerce ((coerce a) <> (coerce b :: V'))
valueMerging = (<>)
}}}
I know using `coerce` isn't the prettiest thing, but notice that with the
value-merging `Monoid` instance, we are able to leverage the automatic
lifting of `Monoid` instances to give us a desired behavior. With the
left-biased instance, we're stuck doing this by hand.
--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/1460#comment:9>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler
More information about the ghc-tickets
mailing list