In Haskell, you define a Monoid as a class.

class (Semigroup a) => Monoid a where mempty :: a mappend = (<>)

and Semigroup is defined as such:

class Semigroup a where (<>) :: a -> a -> a

This definition is beneficial since there is a formal hierarchy that extends fairly nicely into future data-types.

However, a Monoid, Mathamatically, is defined using special properties that are vital to their understanding. A Monoid is a Semigroup that contains an element which acts as and ‘identity’ for every other element within that semigroup. You define this element yourself in Haskell, however, Haskell has no way of checking for you that:

forall a. mempty <> a == a <> mempty == a.

This is a problem. As more people write more and more instances of the Monoid class, how are we suppose to know that these properties are held at all? There is no way to know besides testing, and testing is time consuming and also open for human error.

Solution? Dependent types!

With dependent types, types are powerful enough to describe properties/proofs and we can require that certain types be inhabited by a proof in order for us to use said type.

Let’s start with describing a Monoid.

The elements that belong to a Monoid is a Set/Type, a closed binary operator that works over this Set/Type, and an element of said Set/Type that will be the id-element. Here is our current Description in Coq:

Inductive Monoid (A : Type) (f : A -> A -> A) (id : A) := …

So what is so special about a Monoid? It is both a SemiGroup and ‘id’ serves as an identity operator. So let’s have this proof be a constructor for this type.

Inductive Monoid (A : Type) (f : A -> A -> A) (id : A) :=

| Monoid_proof : SemiGroup A f -> ID_Elem A f id -> Monoid A f id

.

What does it mean to be a SemiGroup? A SemiGroup a Set/Type that has a closed and associative binary operator:

Inductive SemiGroup (A : Type) (f : A -> A -> A) :=

| SemiGroup_proof : Assoc A f -> SemiGroup A f

.

What does how do we describe associativity with types? We do it as such:

Inductive Assoc (A : Type) (f : A -> A -> A) :=

| Assoc_proof : (forall a b c : A, f ( f a b ) c = f a ( f b c ))

-> Assoc A f

.

Notice how the ‘Assoc_proof’ constructor takes a proof and returns the ‘Assoc’ type?

Now, let’s do the same for the identity element:

Inductive ID_Elem (A : Type) (f : A -> A -> A) (id : A) :=

| ID_Elem_proof : (forall a : A, f id a = a) ->

(forall a : A, f a id = a) ->

ID_Elem A f id

.

Cool! We have the most abstract layers of a basic Monoid down now.

For an example, here is a basic type and function that represents Natural Numbers and Addition (Sorry, I know, everyone who has learned Coq has probably done/read this a thousand times):

Inductive Nat : Set :=

| Z : Nat

| S : Nat -> Nat. Fixpoint add (n m : Nat) : Nat :=

match n with

| Z => m

| S n’ => S (add n’ m)

end.

here are some written proofs over this Type and function:

(*associativity*)

Theorem add_assoc :

forall a b c : Nat , add ( add a b ) c = add a ( add b c).

Proof.

induction a.

simpl.

auto.

simpl.

intros.

rewrite IHa.

auto.

Qed. (* id element Left*)

Theorem add_id_Left : forall n : Nat, add Z n = n.

Proof.

intros.

simpl.

auto.

Qed. (* id element Right*)

Theorem add_id_Right : forall n : Nat, add n Z = n.

Proof.

intros.

induction n.

simpl.

auto.

simpl.

rewrite IHn.

auto.

Qed.

We then create a Monoid over Natural Addition:

Definition add_monoid :=

Monoid_proof Nat add Z

(SemiGroup_proof Nat add (Assoc_proof Nat add add_assoc))

(ID_Elem_proof Nat add Z add_id_Left add_id_Right)

.

Thus, the verified Monoid over Natural Addition.

As for another example that will soon interwine with what we just did here, we can now produce a more correct fold function. The fold has been a good tool for describing and using Monoids in everyday situations. Along with the List data-type, it is so:

Inductive List (A : Type) :=

| Empty : List A

| Cons : A -> List A -> List A

. Fixpoint fold (A : Type) (f : A -> A -> A) (empty : A)

(p : Monoid A f empty) (ls : List A) :=

match ls with

| Empty _ => empty

| Cons _ val rest => f val (fold A f empty p rest)

end .

See how before fold could be applied to a list, it must be provided a proof? This is how we know this fold works.

As the final example, the additive fold:

Definition fold_add := fold Nat add Z add_monoid.

Run test computation:

Compute (fold_add (Cons _ (S Z) (Cons _ (S Z) (Empty _)))).

Output:

= S (S Z)

: Nat

Finally, we can sleep at night knowing we can type check our Monoids. More importantly, we can type most things using dependent types, saving us all time and keeping us away from danger.