I'm trying this (for learning purposes):
{-# LANGUAGE FlexibleInstances #-}
instance Monoid (a -> a) where
mempty = id
mappend f g = f . g
expecting id <> id
to be equal to id . id
However, with (id <> id) 1
I receive this error:
Non type-variable argument in the constraint: Monoid (a -> a)
What should I change to run it?
It's just to understand monoids and Haskell typeclasses better, not for any practical usage.
The Haskell
Category
class offers methods to work with categories whose objects are precisely the Haskell types of some kind. Specifically,The names of the methods should look very familiar. Notably,
You probably know that monoids are semigroups with identities, expressed in Haskell using
But another mathematical perspective is that they're categories with exactly one object. If we have a monoid, we can easily turn it into a category:
Going the other way is a little bit trickier. One way to do it is to choose a kind with exactly one type, and look at categories whose sole object is that type (prepare for yucky code, which you can skip if you like; the bit below is less scary). This shows that we can freely convert between a
Category
whose object is the type'()
in the()
kind and aMonoid
. The arrows of the category become the elements of the monoid.But this is yucky! Ew! And pinning things down so tightly doesn't usually accomplish anything from a practical perspective. But we can get the functionality without so much mess, by playing a little trick!
Instead of confining ourselves to a
Category
that really only has one object, we simply confine ourselves to looking at one object at a time.The existing
Monoid
instance for functions makes me sad. I think it would be much more natural to use aMonoid
instance for functions based on theirCategory
instance, using theCat'
approach:Since there's already a
Monoid
instance, and overlapping instances are evil, we have to make do with anewtype
. We could just useand then write
and for some purposes maybe this is the way to go, but since we're using a
newtype
already we usually might as well drop the non-standard equality context and useData.Monoid.Endo
, which builds that equality into the type:This will need
{-# OVERLAPPING #-}
pragma since GHC.Base has an instance forMonoid (a -> b)
when b is a Monoid:then, above instance will be invoked for
a -> a
, even ifa
is a Monoid:whereas with
Monoid b => a -> b
the instance from GHC.Base will be invoked:Note that
Data.Monoid
provides an exact same instance as yours fora -> a
but there the overlap is bypassed usingnewtype Endo a
.