The default monoid for lists in the GHC Prelude is concatenation.
[1,2,3] <> [4,5,6]
becomes [1,2,3] ++ [4,5,6]
and thus [1,2,3,4,5
First note that ZipList
’s Applicative
instance already has the zippy behaviour you want.
ghci> liftA2 (<>) (Sum <$> ZipList [1,2,3]) (Sum <$> ZipList [4,5,6]) :: ZipList Int
ZipList [Sum 5, Sum 7, Sum 9]
Then use the fact that any Applicative
gives rise to a Monoid
by lifting the monoidal behaviour of its contents through the monoidal functor itself. The plan is to abstract the liftA2 (<>)
pattern from the expression I wrote above.
newtype Ap f a = Ap { getAp :: f a }
instance (Applicative f, Monoid a) => Monoid (Ap f a) where
mempty = Ap $ pure mempty
Ap xs `mappend` Ap ys = Ap $ liftA2 mappend xs ys
(As far as I know this newtype
is missing from base
, which seems like an oversight to me, though there may be a good reason for it. In fact, I’d argue that ZipList
should have a zippy Monoid
instance out of the box, but, alas, it doesn’t.)
Your desired Monoid
is then just Ap ZipList (Sum Int)
. This is equivalent to the MyZipList
Monoid
you wrote by hand (except for the mistake in your mempty
- it should be MyZipList $ ZipList $ repeat mempty
), but composing it out of reusable newtype
s like this is less ad-hoc and requires less boilerplate.