• Docència
    • Àlgebra Lineal i Geometria
    • Recull de qüestions
    • Recursos
    • Direcció de treballs
    • Innovació Docent
  • Recerca
    • Publicacions
    • Conferències
  • Codi
    • An Introduction to Lean 4
    • Taller de Lean 4
    • Galeria TikZ
    • Hipotrocoide
    • Evolució espacial dels virus
  • Enllaços
    • Grup d’Investigació
    • Grup d’Innovació Docent
    • Seminari Predoc
    • Congrés Investmat
  1. Lists and Monoids
  • Source Code
  • An Introduction to Lean 4
  • Basic Syntax
  • Propositions
  • Quantifiers
  • Equalities
  • Functions
  • Choice
  • Subtypes
  • Relations
  • Quotients
  • Natural numbers
  • Orders
  • Empty and Unit types
  • Product and Sum types
  • Lists and Monoids

Sections

  • Lists
  • Monoids
    • Examples of monoids
    • The free monoid over type α
    • The universal property
    • The length of a list
  • Exercises

Lists and Monoids

Autor

Enric Cosme

Modificat

06/05/2025

In this chapter, we explore the foundational concept of monoids and their deep connection to lists, one of the most fundamental data structures in both mathematics and computer science. We begin by examining lists as sequences of elements drawn from a type α, highlighting their structure and operations such as concatenation and the empty list. Building on this, we introduce monoids—algebraic structures consisting of a list equipped with an associative binary operation and an identity element.

We will see that addition and multiplication over the natural numbers naturally form monoids. We then introduce the free monoid over a type α and examine its defining properties. A central focus of the chapter is the universal property of the free monoid, which characterizes it as the most general monoid generated by a type of elements.

We conclude the theoretical discussion by applying the universal property to define the length of a list as a monoid homomorphism into the natural numbers with addition. This example showcases the practical utility of the abstract theory. Finally, the chapter ends with a set of exercises designed to reinforce the concepts presented in this chapter.

Lists

In functional programming and formal systems like Lean, a list is a fundamental data structure that represents a sequence of elements of a given type.

If we #print List, Lean returns:

inductive List.{u} : Type u → Type u
number of parameters: 1
constructors:
List.nil : {α : Type u} → List α
List.cons : {α : Type u} → α → List α → List α

The List type is defined as an inductive type. List is a parametric type that takes one type parameter—say, α—and produces the type List α, representing lists of elements of type α. This definition includes two constructors. The first, List.nil, represents the empty list, also written [], meaning it can construct an empty list for any type α. The second constructor, List.cons, builds a nonempty list by taking an element of type α, say x, and a list of elements of type α, say xs, returning a new list of type List α, List.cons x xs, also written x :: xs. This new list will have x as its head and the list xs as its tail. This construction makes lists easy to process recursively, as each list is either empty or built by adding an element to the front of another list.

For example, in List N, the expression [] represents the empty list, z :: [] is a list containing a single element—namely [z], and z :: s z :: [] constructs a list with two elements, written as [z, s z].

Using the List.cons constructor we can define the concatenation operation, List.append, which takes two lists l1 and l2 of type List α and returns a new list List.append l1 l2, also written l1 ++ l2, which appends the two lists together. For example, for the lists [z, s z] and [z] in List N, [z, s z] ++ [z] returns the list [z, s z, z].

Monoids

The following Lean code defines the algebraic structure of a monoid and monoid homomorphisms as structure types in Lean.

Monoids.lean
-- A monoid
@[ext] structure Monoid.{u} where
  base : Type u
  mul : base → base → base
  one : base
  assoc : ∀ {a b c : base}, mul a (mul b c) = mul (mul a b) c
  idl : ∀ {a : base}, mul one a = a
  idr : ∀ {a : base}, mul a one = a
-- A monoid homomorphism
@[ext] structure MonoidHom (M N : Monoid) where
  map : M.base → N.base
  map_mul : ∀ {a b : M.base}, map (M.mul a b) = N.mul (map a) (map b)
  map_one : map M.one = N.one

The first structure, Monoid, represents a monoid as a type base equipped with a binary operation mul (interpreted as multiplication), an identity element one, and three axioms. The associativity axiom (assoc) asserts that multiplication is associative: for all elements a, b, and c, we have mul a (mul b c) = mul (mul a b) c. The left identity (idl) and right identity (idr) laws state that the element one behaves as left and right identity for multiplication: mul one a = a and mul a one = a, respectively. The attribute @[ext] enables Lean to automatically generate extensionality lemmas for these structures, making it easier to prove equalities between instances.

The second structure, MonoidHom, formalizes monoid homomorphisms between two monoids M and N. A homomorphism consists of a function map between the underlying sets of M and N, which preserves the monoid operations: it satisfies map (M.mul a b) = N.mul (map a) (map b) for all a, b, and also maps the identity element of M to that of N, i.e., map M.one = N.one. Together, these definitions provide a foundation for reasoning formally about monoids and their structure-preserving maps within Lean’s type theory framework.

Examples of monoids

We present two examples of monoid structures defined over the natural numbers. In the first example, the binary operation is addition, with 0 serving as the identity element. In the second example, the operation is multiplication, and the identity element is 1. Both structures satisfy the monoid axioms.

Monoids.lean
-- (N, +, 0) is a monoid
def instMonoidNAdd : Monoid where
  base  := N
  mul   := Addition
  one   := z
  assoc := TAddAss.symm
  idl   := TAdd0L
  idr   := TAdd0R
-- (N, *, 1) is a monoid
def instMonoidNMul : Monoid where
  base  := N
  mul   := Multiplication
  one   := one
  assoc := TMultAss.symm
  idl   := TMult1L
  idr   := TMult1R

The free monoid over type α

For any type α, the free monoid over α is given by the type List α, equipped with list concatenation (++) as the binary operation and the empty list [] as the identity element. This structure forms a monoid because concatenation is associative and the empty list acts as a neutral element for concatenation on both sides.

FreeMonoid.lean
-- (List α, ++, []) is a monoid for any type α
def FreeMonoid {α : Type u} : Monoid where
  base := List α
  mul := List.append
  one := []
  assoc := by
    intro a b c
    induction a with
      | nil => simp [List.append]
      | cons x xs ih => simp [List.append, ih]
  idl := by
    intro a
    induction a with
      | nil => simp [List.append]
      | cons x xs ih => simp [List.append, ih]
  idr := by
    intro a
    induction a with
      | nil => simp [List.append]
      | cons x xs ih => simp [List.append, ih]

The universal property

The following Lean code defines the canonical insertion of generators function η from a type α into List α.

FreeMonoid.lean
-- Insertion of generators
def η {α : Type u} : α → (@FreeMonoid α).base := by
  intro a
  exact List.cons a []

The function η takes an element a : α and returns the singleton list [a], implemented here as List.cons a []. This reflects the standard way of embedding generators into a free monoid: each element of α is mapped to a list containing just that element.

The universal property of the FreeMonoid α states that for any monoid M and any function f : α → M.base, there exists a unique monoid homomorphism Lift f : FreeMonoid α → M such that Lift f ∘ η = f. This means that Lift f extends f in a way that respects the monoid structure, making FreeMonoid α the most general monoid generated freely by the elements of α.

The definition of Lift f is defined recursively on lists, as follows:

FreeMonoid.lean
def Lift {α : Type u} {M : Monoid} (f : α → M.base) : (@FreeMonoid α).base → M.base := by
  intro xs
  cases xs with
    | nil => exact M.one
    | cons x xs => exact M.mul (f x) (Lift f xs)

The base case corresponds to the empty list: Lift f [] = M.one, ensuring the identity element of the monoid is preserved. For non-empty lists, Lift f applies the homomorphism recursively to the tail of the list and then combines it with the image of the head element using the monoid multiplication M.mul. Specifically, for a list x::xs', we have Lift f (x::xs) = M.mul (f x) (Lift f xs).

This construction guarantees that Lift f is a monoid homomorphism, mapping the empty list to the identity element and preserving the monoid operation, as we can prove below.

FreeMonoid.lean
-- The function `Lift f` is a monoid homomorphism from the free monoid to the monoid `M`
def LiftMonoidHom {α : Type u} {M : Monoid} (f : α → M.base) : MonoidHom (@FreeMonoid α) M where
  map := Lift f
  map_mul := by
    intro a b
    induction a with
      | nil => calc
        Lift f (FreeMonoid.mul [] b) = Lift f b                     := rfl
        _                            = M.mul (M.one) (Lift f b)     := M.idl.symm
        _                            = M.mul (Lift f []) (Lift f b) := congrArg (fun x => M.mul x (Lift f b)) rfl
      | cons x xs ih => calc
        Lift f (FreeMonoid.mul (x::xs) b) = Lift f (x :: (FreeMonoid.mul xs b))         := rfl
        _                                 = M.mul (f x) (Lift f (FreeMonoid.mul xs b))  := rfl
        _                                 = M.mul (f x) (M.mul (Lift f xs) (Lift f b))  := congrArg (fun y => M.mul (f x) y) ih
        _                                 = M.mul (M.mul (f x) (Lift f xs)) (Lift f b)  := M.assoc
        _                                 = M.mul (Lift f (x::xs)) (Lift f b)           := congrArg (fun y => M.mul y (Lift f b)) rfl
  map_one := rfl

Furthermore, Lift f extends f in the sense that for each element a : α, it satisfies Lift f (η a) = f a, where η is the insertion map that sends a to the singleton list [a]. This is proven in the theorem below.

FreeMonoid.lean
theorem LiftEta {α : Type u} {M : Monoid} (f : α → M.base) : Lift f ∘ η = f := by
  funext a
  calc
    Lift f (η a) = Lift f (a::[])           := rfl
    _            = M.mul (f a) (Lift f [])  := rfl
    _            = M.mul (f a) M.one        := congrArg (fun x => M.mul (f a) x) rfl
    _            = f a                      := M.idr

Finally, we can prove that the function Lift f is the unique monoid homomorphism from the free monoid FreeMonoid α to any monoid M that satisfies the property Lift f ∘ η = f. This is proven in the theorem below.

FreeMonoid.lean
theorem LiftUnique {α : Type u} {M : Monoid} (f : α → M.base) 
(g : MonoidHom (@FreeMonoid α) M) : g.map ∘ η = f → g = LiftMonoidHom f := by
  intro h
  apply MonoidHom.ext
  funext a
  induction a with
    | nil => calc
      g.map []  = M.one := g.map_one
      _         = (LiftMonoidHom f).map [] := (LiftMonoidHom f).map_one
    | cons x xs ih => calc
      g.map (x::xs) = g.map (FreeMonoid.mul (η x) xs)       := rfl
      _ = M.mul (g.map (η x)) (g.map xs)                    := g.map_mul
      _ = M.mul (f x) (g.map xs)                            := congrArg (fun y => M.mul y (g.map xs)) (congrFun h x)
      _ = M.mul ((LiftMonoidHom f).map (η x)) (g.map xs)    := congrArg (fun y => M.mul y (g.map xs)) (congrFun (LiftEta f).symm x)
      _ = M.mul ((LiftMonoidHom f).map (η x)) ((LiftMonoidHom f).map xs) := congrArg (fun y => M.mul ((LiftMonoidHom f).map (η x)) y) ih
      _ = (LiftMonoidHom f).map (FreeMonoid.mul (η x) xs)                := (LiftMonoidHom f).map_mul.symm
      _ = (LiftMonoidHom f).map (x::xs)                                  := rfl

The length of a list

As an application of the universal property of the free monoid, we define a function Length that computes the length of a list. First, we define Len : α → N, a function that maps each element of type α to the natural number 1, representing the fact that each element in a list contributes exactly one to the length.

Length.lean
def Len : α → N := by
  intro _
  exact one

Using the universal property of the free monoid, we extend Len to a monoid homomorphism Length : (List α,++,[]) → (N,+,0) by applying the Lift function.

Length.lean
def Length {α : Type u} : (@FreeMonoid α).base → instMonoidNAdd.base := Lift Len

This guarantees that Length satisfies the required monoid homomorphism properties: it maps the empty list to 0 (the identity in (N,+,0)), and for any two lists, the length of their concatenation is the sum of their individual lengths. Thus, Length is a monoid homomorphism that respects the structure of the free monoid and computes the number of elements in a list. This example highlights how the universal property of the free monoid enables the definition of homomorphisms that extend functions from the generators to any target monoid.

Exercises

Exercises.lean
-- The definition of Monoid Isomorphism
@[ext] structure MonoidIso (M N : Monoid) extends (MonoidHom M N) where
  iso : isomorphism map
-- Prove that the monoids `(N,+,0)` and `(List Unit,++,[])` are isomorphic
def NFreeMonoidIso : MonoidIso (@FreeMonoid Unit) instMonoidNAdd where
  sorry

2025 Enric Cosme Llópez

Dr. Moliner, 50 | 46100, Burjassot, València

Dissenyat amb & Quarto