In this lecture, we explore basic algebraic structures. The group project will be conducted in that direction. If you prefer to work on tactics and proofs, here are two suggestions:
For convenience, we call a curried function of signature X → X → X an operation on X.
def Op (X : Type) : Type := X → X → X
X, an operation μ : Op X, and a proof that μ is associative.⟨X, μ, a⟩ where a is a proof that the operation μ : Op X on the type X is associative, meaning that a is a term of type ∀ x y z : X, μ (μ x y) z = μ x (μ y z).μ be a proposition is to guarantee that we can identify ⟨X, μ, a⟩ and ⟨X, μ, a'⟩ whenever a and a' are both proofs of the associativity property of μ.If X : Type and μ : Op X, we introduce the following predicate on Op X.
def Op.isAssociative {X : Type} (μ : Op X) : Prop :=
∀ x y z : X, μ (μ x y) z = μ x (μ y z)
X as an implicit parameter here. This indeed makes Op.isAssociative a function of type signature Op X → Prop.Op X for each X, we can use dot notation and write μ.isAssociative instead of Op.isAssociative μ.We can then define the type Semigroup as a structure (which we can think of for now as an inductive type with only one constructor).
structure Semigroup where
carrier : Type
op : Op carrier
assoc : op.isAssociative
S is a dependent triple with three components: S = ⟨S.carrier, S.op, S.assoc⟩. The term S.assoc is a proof of S.op.isAssociative.How do we construct a semigroup with "underlying magma" ⟨Nat, Nat.add⟩? By definition, we have to prove the associativity property of Nat.add : Nat → Nat → Nat.
def NatAddSemigroup : Semigroup where
carrier := Nat -- input by user
op := Nat.add -- input by user
assoc := Nat.add_assoc -- associativity proof, found by `exact?`
where helps to make things explicit (compared to using :=). After :=, you would need to write ⟨Nat, Nat.add, Nat.add_assoc⟩, which is in fact notation for Semigroup.mk Nat Nat.add Nat.add_assoc.(· + ·) as notation for Nat.add.If we want to say that an operation has a neutral element, we need an extra parameter, namely the element itself.
def Op.hasNeutral {X : Type} (μ : Op X) (e : X) : Prop :=
∀ x : X, μ e x = x ∧ μ x e = x
The expression NatAddSemigroup.op.hasNeutral (0 : Nat) now represents the property that (0 : Nat) is a neutral element for the operation Nat.add : Op Nat.
example : NatAddSemigroup.op.hasNeutral (0 : Nat) = ∀ n : Nat, 0 + n = n ∧ n + 0 = n := rfl
Note that NatAddSemigroup.op.hasNeutral (42 : Nat) also type-checks and that the type ∀ n : Nat, 42 + n = n ∧ n + 42 = n is well-formed but not inhabited.
Let us define monoids as semigroups equipped with an element and a proof that that element is neutral. We can do that by extending the Semigroup structure (giving us access to the carrier and op fields).
structure Monoid extends Semigroup where
neutral_elt : carrier
neutral_ppty : op.hasNeutral neutral_elt
toSemigroup.carrier and toSemigroup.op.M is represented by a dependent tuple with five components M = ⟨X, μ, a, e, n⟩, where X : Type, μ : Op X, a is a proof of μ.isAssociative, e : X, and n is a proof of μ.hasNeutral e.In the project, you will be asked to construct a monoid with associated semigroup the term NatAddSemigroup constructed earlier. This will go as follows:
def NatAddZeroMonoid : Monoid where
toSemigroup := NatAddSemigroup
neutral_elt := sorry
neutral_ppty := sorry
NatAddSemigroup. We could also fill out the fields carrier, op and assoc, of the Semigroup structure.μ.hasNeutral as ∃ e : X, ∀ x : X, (μ e x = x) ∧ (μ x e = x). Existential quantifiers must be treated with care.⟨S, e, n⟩ and ⟨S', e', n'⟩ are equal if and only if S = S' as semigroups (the main reason for this is that if e and e' are neutral elements in the same semigroup S, then e = e', which we will prove formally in the project).From the Semigroup structure, we can project to the carrier, op and assoc fields of the structure.
#check Semigroup.assoc -- Semigroup.assoc (self : Semigroup) : self.op.isAssociative
#check NatAddSemigroup.assoc -- NatAddSemigroup.assoc : NatAdd.op.isAssociative
Similarly, from the Monoid structure, we get projections Monoid.neutral_elt and Monoid.neutral_ppty. We also get a function Monoid → Semigroup, which formalises the fact that the structure Monoid extends the structure Semigroup.
#check @Monoid.toSemigroup -- Monoid.toSemigroup : Monoid → Semigroup
Semigroup structure to a structure CommSemigroup, with an additional field comm : op.isCommutative, where the predicate Op.isCommutative is defined by ∀ x y : X, μ x y = μ y x.f : Semigroup → T and a term cS : CommSemigroup, we might then want the expression f cS to automatically type-check (without having to write f cS.toSemigroup). This can be achieved by implementing a coercion. The automatization is taken care of via a mechanism called type-class inference.coe : CommSemigroup → Semigroup, our expression f cS will automatically be interpreted as f (coe cS).instance : Coe CommSemigroup Semigroup where
coe := CommSemigroup.toSemigroup
α and a structure β extending α, we can use the projection β → α as a coercion from β to α. Thanks to a type-class inference mechanism, functions defined on α then become automatically applicable on β.In the project file, you will be asked to:
μ : Op X is an operation that admits a neutral element, then such an element is unique.NatAddSemigroup.Here is the link to the Lean file for the group project:
--- ## Feedback on the workshop? And link to project file? Before we start working on the project files, I would like to ask for 5 minutes of your time, to give feedback on the workshop if you have not done it yet:  [Online survey](https://uni-heidelberg.evasys.de/evasys/online.php?p=WXQ8W) Thanks! :pray: :blush:
4. Formalise the definition of a group (by extending the `Monoid` structure). 5. Construct a group with underlying magma `⟨Int, (· + ·)⟩`. 6. Extend the definitions of monoids and groups to the commutative case and prove that `⟨Int, (· + ·)⟩` is a commutative group.