14. Relations in Lean

In the last chapter, we noted that set theorists think of a binary relation \(R\) on a set \(A\) as a set of ordered pairs, so that \(R(a, b)\) really means \((a, b) \in R\). An alternative is to think of \(R\) as a function which, when applied to \(a\) and \(b\), returns the proposition that \(R(a, b)\) holds. This is the viewpoint adopted by Lean: a binary relation on a type A is a function A A Prop. Remember that the arrows associate to the right, so A A Prop really means A (A Prop). So, given a : A, R a is a predicate (the property of being related to A), and given a b : A, R a b is a proposition.

14.1. Order Relations

With first-order logic, we can say what it means for a relation to be reflexive, symmetric, transitive, antisymmetric, and so on:

namespace hidden

variable {A : Type}

def Reflexive (R : A  A  Prop) : Prop :=
 x, R x x

def Symmetric (R : A  A  Prop) : Prop :=
 x y, R x y  R y x

def Transitive (R : A  A  Prop) : Prop :=
 x y z, R x y  R y z  R x z

def AntiSymmetric (R : A  A  Prop) : Prop :=
 x y, R x y  R y x  x = y

def Irreflexive (R : A  A  Prop) : Prop :=
 x, ¬ R x x

def Total (R : A  A  Prop) : Prop :=
 x y, R x y  R y x

end hidden

Notice that Lean will unfold the definitions when necessary, for example, treating Reflexive R as x, R x x.

variable (R : A  A  Prop)

example (h : Reflexive R) (x : A) : R x x := h x

example (h : Symmetric R) (x y : A) (h1 : R x y) : R y x :=
h x y h1

example (h : Transitive R) (x y z : A) (h1 : R x y) (h2 : R y z) :
  R x z :=
h x y z h1 h2

example (h : AntiSymmetric R) (x y : A) (h1 : R x y)
    (h2 : R y x) :
  x = y :=
h x y h1 h2

In the command variable {A : Type}, we put curly braces around A to indicate that it is an implicit argument, which is to say, you do not have to write it explicitly; Lean can infer it from the argument R. That is why we can write Reflexive R rather than Reflexive A R: Lean knows that R is a binary relation on A, so it can infer that we mean reflexivity for binary relations on A.

Given h : Transitive R, h1 : R x y, and h2 : R y z, it is annoying to have to write h x y z h1 h2 to prove R x z. After all, Lean should be able to infer that we are talking about transitivity at x, y, and z, from the fact that h1 is R x y and h2 is R y z. Indeed, we can replace that information by underscores:

variable (R : A  A  Prop)

example (h : Transitive R) (x y z : A) (h1 : R x y)
    (h2 : R y z) :
  R x z :=
h _ _ _ h1 h2

But typing underscores is annoying, too. The best solution is to declare the arguments x y z to a transitivity hypothesis to be implicit as well. We can do this by introducing curly braces around the variables in the definition.

def Transitive' (R : A  A  Prop) : Prop :=
 {x} {y} {z}, R x y  R y z  R x z

def Transitive (R : A  A  Prop) : Prop :=
 {x y z}, R x y  R y z  R x z

variable (R : A  A  Prop)

example (h : Transitive R) (x y z : A) (h1 : R x y)
    (h2 : R y z) :
  R x z :=
h h1 h2

In fact, the notions Reflexive, Symmetric, Transitive, and so on are defined in Mathlib in exactly this way, so we are free to use them by doing import Mathlib.Init.Logic at the top of the file.

import Mathlib.Init.Logic

#check Reflexive
#check Symmetric
#check Transitive
#check AntiSymmetric
#check Irreflexive

We put our temporary definitions of in a namespace hidden; that means that the full name of our version of Reflexive is hidden.Reflexive, which would not conflict with the one defined in the library were we to import that module.

In Section 13.1 we showed that a strict partial order - that is, a binary relation that is transitive and irreflexive - is also asymmetric. Here is a proof of that fact in Lean.

example (h1 : Irreflexive R) (h2 : Transitive R) :
     x y, R x y  ¬ R y x := by
  intro x y
  intro (h3 : R x y)
  intro (h4 : R y x)
  have h5 : R x x := h2 h3 h4
  have h6 : ¬ R x x := h1 x
  show False
  exact h6 h5
variable A : Type
variable R : A  A  Prop

In mathematics, it is common to use infix notation and a symbol like to denote a partial order, which you can input by typing \preceq. Lean supports this practice:

section
variable (A : Type)
variable (R : A  A  Prop)

-- type \preceq for the symbol ≼
local infix:50 " ≼ " => R

example (h1 : Irreflexive R) (h2 : Transitive R) :
     x y, x  y  ¬ y  x := by
  intro x y
  intro (h3 : x  y)
  intro (h4 : y  x)
  have h5 : x  x := h2 h3 h4
  have h6 : ¬ x  x := h1 x
  show False
  exact h6 h5

end

The structure of a partial order consists of a type A (traditionally a set A) with a binary relation le : A A Prop (short for “lesser or equal”) on it that is reflexive, transitive, and antisymmetric. We can package this structure as a “class” in Lean.

class PartialOrder (A : Type u) where
  le : A  A  Prop
  refl : Reflexive le
  trans : Transitive le
  antisymm :  {a b : A}, le a b  le b a  a = b

-- type \preceq for the symbol ≼
local infix:50 " ≼ " => PartialOrder.le

Assuming we have a type A that is a partial order, we can define the corresponding strict partial order lt : A A Prop (short for “lesser than”) and prove that it is, indeed, a strict order. We also introduce notation for le, which you can write by typing \prec.

namespace PartialOrder
variable {A : Type} [PartialOrder A]

def lt (a b : A) : Prop := a  b  a  b

-- type \prec for the symbol ≺
local infix:50 " ≺ " => lt

theorem irrefl_lt (a : A) : ¬ (a  a) := by
  intro (h : a  a)
  have : a  a := And.right h
  have : a = a := rfl
  contradiction

theorem trans_lt {a b c : A} (h₁ : a  b) (h₂ : b  c) : a  c :=
  have : a  b := And.left h₁
  have : a  b := And.right h₁
  have : b  c := And.left h₂
  have : b  c := And.right h₂
  have : a  c := trans a  b b  c
  have : a  c :=
    fun hac : a = c 
    have : c  b := by rw [ hac]; assumption
    have : b = c := antisymm b  c c  b
    show False from b  c b = c
  show a  c from And.intro a  c a  c

end PartialOrder

The variable declaration [PartialOrder A] can be read as “assume A is a partial order”. Then Lean will use this “instance” of the class PartialOrder to figure out what le and lt are referring to.

The proofs use anonymous have, referring back to them with the French quotes, `\f< and \f>, or assumption (in tactic mode). The proof of transitivity switches from term mode to tactic mode, to use rewrite to replace c for a in a b. Recall that contradiction instructs Lean to find a hypothesis and its negation in the context, and hence complete the proof.

We could even define the class StrictPartialOrder in a similar manner, then use the above theorems to show that any (weak) PartialOrder is also a StrictPartialOrder.

class StrictPartialOrder (A : Type u) where
  lt : A  A  Prop
  irrefl : Irreflexive lt
  trans : Transitive lt

-- type \prec for the symbol ≺
local infix:50 " ≺ " => StrictPartialOrder.lt

instance {A : Type} [PartialOrder A] : StrictPartialOrder A where
  lt          := PartialOrder.lt
  irrefl      := PartialOrder.irrefl_lt
  trans _ _ _ := PartialOrder.trans_lt

example (a : A) [PartialOrder A] : ¬ a  a :=
StrictPartialOrder.irrefl a

Once we have shown this instance, we would be able to use the inherited (not the one we defined in the PartialOrder namespace!) and facts about StrictPartialOrder on any partial order.

In Section Section 13.1, we also noted that you can define a (weak) partial order from a strict one. We ask you to do this formally in the exercises below.

Mathlib defines PartialOrder in roughly the same way as we have, which is why we enclosed our definitions in the hidden namespace, so that our definition is called hidden.PartialOrder rather than just PartialOrder outside the namespace. There is no StrictPartialOrder definition, but we can refer to the strict partial order, given a partial order. The notation used by Mathlib is the more common (input \le) and <.

Here is one more example. Suppose R is a binary relation on a type A, and we define S x y to mean that both R x y and R y x holds. Below we show that the resulting relation is reflexive and symmetric.

section
axiom A : Type
axiom R : A  A  Prop

variable (h1 : Transitive R)
variable (h2 : Reflexive R)

def S (x y : A) := R x y  R y x

example : Reflexive S :=
fun x 
  have : R x x := h2 x
  show S x x from And.intro this this

example : Symmetric S :=
fun x y 
fun h : S x y 
have h1 : R x y := h.left
have h2 : R y x := h.right
show S y x from h2, h1

end

In the exercises below, we ask you to show that S is transitive as well.

In the first example, we use the anonymous have, and then refer back to the have with the keyword this. In the second example, we abbreviate And.left h and And.right h as h.left and h.right, respectively. We also abbreviate And.intro h2 h1 with an anonymous constructor, writing ⟨h2, h1⟩. Lean figures out that we are trying to prove a conjunction, and figures out that And.intro is the relevant introduction principle. You can type the corner brackets with \< and \>, respectively.

14.2. Orderings on Numbers

Conveniently, Lean has the normal orderings on the natural numbers, integers, and so on defined already in Mathlib.

import Mathlib.Data.Nat.Defs

open Nat
variable (n m : )

#check 0  n
#check n < n + 1

example : 0  n := Nat.zero_le n
example : n < n + 1 := lt_succ_self n

example (h : n + 1  m) : n < m + 1 :=
have h1 : n < n + 1 := lt_succ_self n
have h2 : n < m := lt_of_lt_of_le h1 h
have h3 : m < m + 1 := lt_succ_self m
show n < m + 1 from lt_trans h2 h3

There are many theorems in Lean that are useful for proving facts about inequality relations. We list some common ones here.

import Mathlib.Order.Basic

variable (A : Type) [PartialOrder A]
variable (a b c : A)

#check (le_trans : a  b  b  c  a  c)
#check (lt_trans : a < b  b < c  a < c)
#check (lt_of_lt_of_le : a < b  b  c  a < c)
#check (lt_of_le_of_lt : a  b  b < c  a < c)
#check (le_of_lt : a < b  a  b)

Notice that we assume an instance of PartialOrder on A. There are also properties that are specific to some domains, like the natural numbers:

import Mathlib.Data.Nat.Defs

variable (n : )

#check (Nat.zero_le :  n : , 0  n)
#check (Nat.lt_succ_self :  n : , n < n + 1)
#check (Nat.le_succ :  n : , n  n + 1)

14.3. Equivalence Relations

In Section 13.3 we saw that an equivalence relation is a binary relation on some domain \(A\) that is reflexive, symmetric, and transitive. We will see such relations in Lean in a moment, but first let’s define another kind of relation called a preorder, which is a binary relation that is reflexive and transitive. Again, we use a class to capture this data.

import Mathlib.Order.Basic

namespace hidden

variable {A : Type}

class Preorder (A : Type u) where
  le : A  A  Prop
  refl : Reflexive le
  trans : Transitive le

end hidden

Lean’s library provides its own formulation of preorders. In order to use the same name, we have to put it in the hidden namespace. Lean’s library defines other properties of relations, such as these:

Building on our definition of a preorder, we can describe partial orders as antisymmetric preorders, and equivalences as symmetric preorders.

 import Mathlib.Order.Basic

 namespace hidden

 variable {A : Type}

 class Preorder (A : Type u) where
   le : A  A  Prop
   refl : Reflexive le
   trans : Transitive le

 class PartialOrder (A : Type u) extends Preorder A where
   antisymm : AntiSymmetric le

class Equivalence (A : Type u) extends Preorder A where
  symm : Symmetric le

 end hidden

The extends Preorder A in this definition now makes PartialOrder a class that automatically inherits all the definitions and theorems from Preorder. In particular le, refl, and trans become part of the data of PartialOrder. Using classes in this way we can write very general proofs (for example proofs just about preorders) and apply them in contexts that are more specific (for example proofs about equivalence relations and partial orders).

Note: we might not want to design the library in this way specifically. Since we might want to use as notation for a partial order, but for an equivalence relation. Indeed Mathlib does not define Equivalence as an extension of PartialOrder.

In Section 13.3 we claimed that there is another way to define an equivalence relation, namely as a binary relation satisfying the following two properties:

  • \(\forall a \; (a \equiv a)\)

  • \(\forall {a, b, c} \; (a \equiv b \wedge c \equiv b \to a \equiv c)\)

We derive the two definitions from each other in the following

 import Mathlib.Order.Basic

 namespace hidden

 class Equivalence (A : Type u) where
   R : A  A  Prop
   refl : Reflexive R
   symm : Symmetric R
   trans : Transitive R

 local infix:50 " ~ " => Equivalence.R

 class Equivalence' (A : Type u) where
   R : A  A  Prop
   refl : Reflexive R
   trans' :  {a b c}, R a b  R c b  R a c

 -- type ``≈`` as ``\~~``
 local infix:50 " ≈ " => Equivalence'.R

 section
 variable {A : Type u}

 theorem Equivalence.trans' [Equivalence A] {a b c : A} :
     a ~ b  c ~ b  a ~ c := by
   intro (hab : a ~ b)
   intro (hcb : c ~ b)
   apply trans hab
   show b ~ c
   exact symm hcb

 example [Equivalence A] : Equivalence' A where
   R := Equivalence.R
   refl := Equivalence.refl
   trans':= Equivalence.trans'

 theorem Equivalence'.symm [Equivalence' A] {a b : A} :
     a  b  b  a := by
   intro (hab : a  b)
   have hbb : b  b := Equivalence'.refl b
   show b  a
   exact Equivalence'.trans' hbb hab

 theorem Equivalence'.trans [Equivalence' A] {a b c : A} :
   a  b  b  c  a  c := by
   intro (hab : a  b) (hbc : b  c)
   have hcb : c  b := Equivalence'.symm hbc
   show a  c
   exact Equivalence'.trans' hab hcb

example [Equivalence' A] : Equivalence A where
  R := Equivalence'.R
  refl := Equivalence'.refl
  symm _ _:= Equivalence'.symm
  trans _ _ _ := Equivalence'.trans

 end
 end hidden

For one of the definitions we use the infix notation ~ and we use for the other. (You can type as \~~.) We use example instead of instance so that we don’t actually instantiate instances of the classes.

14.4. Exercises

  1. Replace the sorry commands in the following proofs to show that we can create a partial order R'​ out of a strict partial order R.

    import Mathlib.Order.Basic
    
    class StrictPartialOrder (A : Type u) where
      lt : A  A  Prop
      irrefl : Irreflexive lt
      trans : Transitive lt
    
    local infix:50 " ≺ " => StrictPartialOrder.lt
    
    section
    variable {A : Type u} [StrictPartialOrder A]
    
    def R' (a b : A) : Prop :=
    (a  b)  a = b
    
    local infix:50 " ≼ " => R'
    
    theorem reflR' (a : A) : a  a := sorry
    
    theorem transR' {a b c : A} (h1 : a  b) (h2 : b  c) :
      a  c :=
    sorry
    
    theorem antisymmR' {a b : A} (h1 : a  b) (h2 : b  a) :
      a = b :=
    sorry
    
    end
    
  2. Replace the sorry by a proof.

    import Mathlib.Order.Basic
    
    namespace hidden
    class Preorder (A : Type u) where
        le : A  A  Prop
        refl : Reflexive le
        trans : Transitive le
    
    namespace Preorder
    def S (a b : A) [Preorder A] : Prop := le a b  le b a
    
    example (A : Type u) [Preorder A] {x y z : A} :
      S x y  S y z  S x z :=
    sorry
    
    end Preorder
    end hidden
    
  3. Only one of the following two theorems is provable. Figure out which one is true, and replace the sorry command with a complete proof.

    import Mathlib.Order.Basic
    
    axiom A : Type
    axiom a : A
    axiom b : A
    axiom c : A
    axiom R : A  A  Prop
    axiom Rab : R a b
    axiom Rbc : R b c
    axiom nRac : ¬ R a c
    
    -- Prove one of the following two theorems:
    
    theorem R_is_strict_partial_order :
      Irreflexive R  Transitive R :=
    sorry
    
    theorem R_is_not_strict_partial_order :
      ¬(Irreflexive R  Transitive R) :=
    sorry
    
  4. Complete the following proof. You may use results from Mathlib.

    import Mathlib.Data.Nat.Defs
    
    section
    open Nat
    variable (n m : )
    
    example : 1  4 :=
    sorry
    
    end