Interlude: Monoids
Before discussing our second abstraction of containers—folds—we need to
introduce another mathematical concept that figures quite prominently in the
Foldable
type class that captures folds: the monoid.
Oh dear, another greek word. What on earth is a monoid? In mathematics, a monoid is a set \(M\) with an operation \(\circ : M \times M \rightarrow M\), often called multiplication. So we can take two elements in \(M\) and multiply them to obtain another element in \(M\), their product. For \((M, \circ)\) to be a monoid, it needs to satisfy two conditions:
-
Identity: There must exist an element \(e \in M\) that is the left and right unit of multiplication: \(e \circ x = x = x \circ e\) for every \(x \in M\).
-
Associativity: For any three elements \(x, y, z \in M\), we need to have \((x \circ y) \circ z = x \circ (y \circ z)\).
Thus, \((\mathbb{Z}, +)\) and \((\mathbb{Z}, *)\) are monoids. Both addition and
multiplication are associative. The unit of addition is 0. The unit of
multiplication is 1. Lists together with list concatenation also form a monoid.
List concatenation is associative. The unit element is the empty list because
prepending or appending it to any list xs
produces xs
. In mathematics, this
list monoid is also known as the free monoid generated by the type of the list
elements.
In Haskell, monoids are captured by the Monoid
type class:1
class Semigroup m => Monoid m where
mappend :: m -> m -> m -- The multiplication operation
mempty :: m -- The unit element
As the class definition says, we actually have Monoid
as a subclass of
Semigroup
, just as in mathematics. In mathematics, a semigroup is the same as
a monoid, but it does not have to have a unit. So there only has to be a
multiplication operation that is associative. A better way to say this is that a
semigroup \((M, \circ)\) is a set \(M\) equipped with an associative multiplication
\(\circ\), and a monoid is a semigroup \((M, \circ)\) with a multiplicative unit.
This concept of a semigroup is captured by the following type class in Haskell
that is a superclass of Monoid
:
class Semigroup m where
(<>) :: m -> m -> m
The default implementation of mappend
then is
mappend = (<>)
because the monoid multiplication is expected to be exactly the same as the semigroup multiplication: a monoid is simply a semigroup with a unit.
Indeed, mappend
is kept mainly for backward compatibility, as the Monoid
type class with functions mappend
and mempty
was added to the Haskell
standard library before factoring out the weaker concept of a semigroup into its
own type class.
The addition and multiplication monoids can be expressed as follows:
newtype Sum a = Sum { getSum :: a }
instance Num a => Semigroup (Sum a) where
Sum x <> Sum y = Sum (x + y)
instance Num a => Monoid (Sum a) where
mempty = Sum 0
newtype Product a = Product { getProduct :: a }
instance Num a => Semigroup (Product a) where
Product x <> Product y = Prod (x * y)
instance Num a => Monoid (Product a) where
mempty = Prod 1
The list monoid is expressed by the following instance
instance Semigroup [a] where
(<>) = (++) -- Multiplication is list concatenation
instance Monoid [a] where
mempty = [] -- The unit is the empty list
All of these monoid instances are defined already in the standard library. The
list monoid is available as part of the Prelude
. To use Sum
and Product
,
you need to import Data.Monoid
.
>>> import Data.Monoid
>>> getSum (Sum 3 <> Sum 5)
8
>>> getProduct (Product 3 <> Product 5)
15
We'll play with these monoids in the next section, as part of our discussion of foldable containers.
-
The naming of the two functions in the
Monoid
type class stems from lists being monoids. For lists, list concatenation, or appending a list to another, is the multiplication operation. Thus, we might call monoid multiplication "monoidal append",mappend
. Similarly, the empty list is the unit for list concatenation, so we may want to call the unit for any monoid the "monoidal empty list",mempty
. ↩