Generically's mappend should agree with the underlying (<>)
There is unfortunate duplication of behaviours with (<>)
and mappend
, their meaning should always coincide but there are ways of making them out of sync. GeneralizedNewtypeDeriving is one way, but I wanted to discuss an error brought to my attention.
{-# Language DerivingVia #-}
{-# Language DerivingStrategies #-}
import Data.Monoid
import Data.Map
import GHC.Generics
-- :set -XOverloadedLists
-- >> Foo [('a', "Hello")] `mappend` Foo [('a', "World")]
-- Foo (fromList [('a',"Hello")])
-- >> Foo [('a', "Hello")] <> Foo [('a', "World")]
-- Foo (fromList [('a',"HelloWorld")])
data Foo = Foo (Map Char String)
deriving stock (Show, Generic)
deriving Monoid via Generically Foo
instance Semigroup Foo where
Foo map <> Foo map1 = Foo (unionWith (<>) map map1)
The desired append operation is clearly the user-defined one: unionWith (<>)
, but Generically Foo
(wrongly) assumes that the user is deriving Semigroup
generically as well: (<>) @(Generically Foo)
.
instance (Generic a, Monoid (Rep a ()) => Monoid (Generically a) where
..
mappend :: Generically a -> Generically a -> Generically a
mappend = (<>) @(Generically a)
Instead, I propose adding a Semigroup a
constraint and making use of whatever (<>)
method already exists. If the user derives (<>)
generically, then mappend
will also be generic. In this example, where the user has specified their own bespoke (<>) = unionWith (<>)
that should be used for mappend
.
We now see that both mappend
and (<>)
produce the same result:
-- >> Foo [('a', "Hello")] `mappend` Foo [('a', "World")]
-- Foo (fromList [('a',"HelloWorld")])
-- >> Foo [('a', "Hello")] <> Foo [('a', "World")]
-- Foo (fromList [('a',"HelloWorld")])
instance (Generic a, Semigroup a, Monoid (Rep a ()) => Monoid (Generically a) where
..
mappend :: Generically a -> Generically a -> Generically a
mappend = coerce do (<>) @a