From Zero to Factorial (part 3)

The basic operations on natural numbers

Frederic Peschanski - LIP6 - Sorbonne Université

This document is copyright (C) 2021 Frederic Peschanski - Creative Commons CC-BY-SA 4.0

Introduction

This is the third installment of our series From Zero to Factorial in which we (re)construct the natural numbers from scratch, assisted by the LaTTe proof assistant. In the first part, we introduced the axioms of Peano arithmetic. This means that we already have defined the set inline_formula not implemented of natural numbers, the number zero, the successor inline_formula not implemented of a natural number inline_formula not implemented, and most importantly the principle of proof by induction on inline_formula not implemented. In the second part, we stated and demonstrated the fundamental theorem of recursion. In the LaTTe proof assistant the theorem was expressed as follows:

(defthm ℕ-rec [[?A :type] [x0 A] [f (==> A A)]]
  (unique (ℕ-rec-prop x0 f)))
Clojure

provided :

(definition ℕ-rec-prop [[?A :type] [x0 A] [f (==> A A)]]
  (λ [rec (==>  A)]
    (and (= (rec zero) x0)
      ( [n ] (= (rec (succ n)) (f (rec n)))))))
Clojure

In more traditional mathematics notation, the theorem says that given a function inline_formula not implemented and inline_formula not implemented, then there exists a unique function inline_formula not implemented such that: inline_formula not implemented

We spent quite some effort to prove this theorem, and in this third part we will make advantage of it to formalize the addition function for natural numbers.

What is addition ?

Addition is probably the first numerical operation we learn, at a very young age. We know, without thinking, that the addition of inline_formula not implemented and inline_formula not implemented gives inline_formula not implemented., or that inline_formula not implemented plus anything gives back that anything, ... In set theory, functions are just subsets, thus the addition is something like :

inline_formula not implemented such that inline_formula not implemented

This is not very useful because this "definition" only appeals to our intuitive understanding of addition. A better approach is to consider a recursive definition. The idea, of course, is to only use what we already defined : zero and the successor function. The following looks like an interesting starting point:

Given inline_formula not implemented, inline_formula not implemented

For example :

inline_formula not implemented

(note that when we say e.g. 3, we actually mean inline_formula not implemented, i.e. we're still in Peano arithmetics)

One slight inconvenience is that the recursion theorem expects one argument, while the addition function seems to expect two. However, we see in the definition that the first argument inline_formula not implemented is kept untouched, so we can rewrite things as follows:

Given inline_formula not implemented, inline_formula not implemented

This looks like a good candidate for the recursion theorem, maybe it's clearer with the following hints:

Given inline_formula not implemented, inline_formula not implemented

Applying the recursion theorem we obtain that the function inline_formula not implemented is unique, for an arbitrary inline_formula not implemented.

The addition function, formally

We now have everything we need to formalize the addition function in LaTTe. Of course, we begin with the function inline_formula not implemented which we define as follows:

(definition plus [m ]
  (q/the (ℕ-rec m succ)))
0.2s
Clojure

The properties of the inline_formula not implemented function, given in the previous section (taking inline_formula not implemented and inline_formula not implemented), corresponds to the following LaTTe expression: (ℕ-rec-prop m succ) . Thanks to the recursion theorem we know that there is a single function satisfying these properties, the uniqueness property being expressed as (ℕ-rec m succ).

Since there is a single object (here, a function) satisfying these properties, it is important to be able to construct it, which relies on the definite descriptor (the) that we discussed in the previous part of the series.

What is important is that the function possesses the expected properties.

(defthm plus-zero [m ]
  (= ((plus m) zero) m))
0.1s
Clojure
(proof 'plus-zero
  (qed (p/and-elim-left (q/the-prop (ℕ-rec m succ)))))
1.1s
Clojure
(defthm plus-succ [m ]
  ( [n ] (= ((plus m) (succ n)) (succ ((plus m) n)))))
0.1s
Clojure
(proof 'plus-succ
  (qed (p/and-elim-right (q/the-prop (ℕ-rec m succ)))))
0.7s
Clojure

These are simple (one liner) consequences of the recursion theorem.

Now, the binary addition operator is simply something like the following:

inline_formula not implemented

The formal definition is as follows:

(definition + [[m ] [n ]]
  ((plus m) n))
0.1s
Clojure

One important take away here is that if the recursion theorem is not so trivial to demonstrate, its effective use, here to define addition, is rather straightforward. I would say that this is a distinctive characteristic of important theorems.

The properties of addition

The intent of our mathematical formulation of addition is to be able to prove important properties, but what properties? There is a whole branch of mathematics that spun off the study of numbers: algebra. The basic properties of the addition operator on natural numbers are elegantly summarized by the notion of a commutative monoid. This means that addition:

  • is associative: inline_formula not implemented

  • has inline_formula not implemented as an identity element: inline_formula not implemented

  • is commutative: inline_formula not implemented

The simplest of these is undoubtedly the identity element, we already (quasi-)proved it!

(defthm zero-identity []
  ( [n ] (= (+ n zero) n)))
0.1s
Clojure
(proof 'zero-identity
  (assume [n ]
    (have <a> (= (+ n zero) n) :by (plus-zero n)))
  (qed <a>))
0.3s
Clojure

Since zero-identity is just a rephrasing of plus-zero, unsurprisingly we can do the same with plus-succ.

(defthm add-succ []
  ( [m n ] (= (+ m (succ n)) (succ (+ m n)))))
0.1s
Clojure
(proof 'add-succ
  (assume [m 
           n ]
    (have <a> _ :by ((plus-succ m) n)))
  (qed <a>))
0.3s
Clojure

Associativity

The property of associativity is of primary importance. It says two important things :

  • addition is not "just" a binary operator, it can be applied to an arbitrary number of operand, e.g. inline_formula not implemented is a legitimate notation

  • the order in which the underlying inline_formula not implemented operations are applied is not significant, e.g. we can "perform" inline_formula not implemented and then inline_formula not implemented or, alternatively inline_formula not implemented and then inline_formula not implemented.

Let's first state the property in LaTTe.

(defthm add-assoc []
  (forall [m n p ] (= (+ (+ m n) p)
                      (+ m (+ n p)))))
0.1s
Clojure

For the proof, it is of course based on the induction principle on natural numbers, but what should apply induction to: inline_formula not implemented, inline_formula not implemented or inline_formula not implemented ? Since there are only three possible choices, we could try them in turn. First, we could first prove commutativity, which would simplify the problem. But it is interesting to highlight the fact that the two properties are in fact independent. So let's try to find an alternative approach. We know that inline_formula not implemented is in fact inline_formula not implemented and thus the right argument is let's say "more" concerned by the induction definition. So a good hint is to prioritize the rightmost argument. In the theorem statement, inline_formula not implemented is always on the left, inline_formula not implemented occurs both on the left and on the right, and inline_formula not implemented is always rightmost. Thus, intuitively it seems that inline_formula not implemented would be our "best" candidate.

So let's try to prove our property by induction on inline_formula not implemented.

  • case inline_formula not implemented

inline_formula not implemented

  • case inline_formula not implemented

Suppose inline_formula not implemented (induction hypothesis).

inline_formula not implemented

Thus the property is true for inline_formula not implemented, which finishes the proof.

Exercise : try to prove associativity by induction on n, by induction on m. Is communtativity needed ?

The formal proof

(proof 'add-assoc
  (assume [m  n ]
    (pose P := (lambda [p ] (= (+ (+ m n) p)
                               (+ m (+ n p)))))
    "Case (P zero)"
    (have <a1> (= (+ (+ m n) zero) (+ m n))
      :by (zero-identity (+ m n)))
    (have <a2> (= (+ m n) (+ m (+ n zero)))
      :by (eq/eq-cong (plus m) (eq/eq-sym (zero-identity n))))
    (have <a> (P zero) :by (eq/eq-trans <a1> <a2>))
    "Case (P (succ p))"
    (assume [p 
             Hind (P p)]
      (have <b1> (= (+ (+ m n) (succ p))
                   (succ (+ (+ m n) p)))
        :by (add-succ (+ m n) p))
      (have <b2> (= (succ (+ (+ m n) p))
                   (succ (+ m (+ n p))))
        :by (eq/eq-cong succ Hind))
      (have <b3> (= (succ (+ m (+ n p)))
                   (+ m (succ (+ n p))))
        :by (eq/eq-sym (add-succ m (+ n p))))
      (have <b4> (= (+ m (succ (+ n p)))
                   (+ m (+ n (succ p))))
        :by (eq/eq-cong (plus m) (eq/eq-sym (add-succ n p))))
      (have <b> (P (succ p)) :by (eq/eq-trans* <b1> <b2> <b3> <b4>)))
    "We apply the induction principle"
    (have <c> _ :by ((ℕ-induct P) <a> <b>)))
  (qed <c>))
15.8s
Clojure

Commutativity

For commutativity, the proof is a little bit more involved, and relies on the induction principle for natural numbers. The property to prove is inline_formula not implemented with inline_formula not implemented, for an arbitrary inline_formula not implemented.

  • case inline_formula not implemented

we have to prove inline_formula not implemented and we already know, by zero-identity, that inline_formula not implemented. We need also to demonstrate that inline_formula not implemented, which requires a proof by induction. We have inline_formula not implemented as a special case of zero-identity, and if we suppose that inline_formula not implemented for an arbitrary inline_formula not implemented, then:

inline_formula not implemented

Hence inline_formula not implemented as expected, and thus inline_formula not implemented is true.

  • case inline_formula not implemented

For an arbitrary inline_formula not implemented we assume inline_formula not implemented as an hypothesis of induction. Now we must demonstrate inline_formula not implemented i.e. inline_formula not implemented.

inline_formula not implemented

The last step cannot be justified yet. We need to show that inline_formula not implemented.

Assuming this (and proved in the next section), we have inline_formula not implemented, which concludes the demonstration

The formal proof

We have almost everything we need to formalize the commutativity of addition in LaTTe.

(defthm add-commute []
  ( [m n ] (= (+ m n) (+ n m))))
0.1s
Clojure

Before starting the proof, let's state and prove the auxiliary lemma that is hinted in the informal proof (case inline_formula not implemented).

(deflemma zero-identity-right []
  ( [n ] (= (+ zero n) n)))
0.1s
Clojure
(proof 'zero-identity-right
  (pose P := (lambda [n ] (= (+ zero n) n)))
  "Case (P zero)"
  (have <a> (P zero) :by (zero-identity zero))
  "Inductive case"
  (assume [n 
           Hind (P n)]
    (have <b1> (= (+ zero (succ n))
                 (succ (+ zero n))) :by (add-succ zero n))
    (have <b2> (= (succ (+ zero n))
                 (succ n)) :by (eq/eq-cong succ Hind))
    (have <b> (P (succ n)) :by (eq/eq-trans <b1> <b2>)))
  "We apply the induction principle."
  (qed ((ℕ-induct P) <a> <b>)))
1.1s
Clojure

The second auxiliary lemma that we need is about a variant of add-succ.

(deflemma add-succ-swap []
  ( [m n ] (= (+ (succ m) n) (succ (+ m n)))))
0.1s
Clojure
(proof 'add-succ-swap
  (assume [m ]
    (pose P := (lambda [n ] (= (+ (succ m) n) (succ (+ m n)))))
    "The proof is by induction on n"  
    "Case (P zero)"
    (have <a1> (= (+ (succ m) zero) (succ m)) :by (zero-identity (succ m)))
    (have <a2> (= (succ m) (succ (+ m zero)))
      :by (eq/eq-cong succ (eq/eq-sym (zero-identity m))))
    (have <a> (P zero) :by (eq/eq-trans <a1> <a2>))
    "Case (P (succ n))"
    (assume [n 
             Hind (P n)]
      (have <b1> (= (+ (succ m) (succ n))
                   (succ (+ (succ m) n))) :by (add-succ (succ m) n))
      (have <b2> (= (succ (+ (succ m) n))
                   (succ (succ (+ m n)))) :by (eq/eq-cong succ Hind))
      (have <b3> (= (succ (succ (+ m n)))
                   (succ (+ m (succ n))))
        :by (eq/eq-cong succ (eq/eq-sym (add-succ m n))))
      (have <b> (P (succ n)) :by (eq/eq-trans* <b1> <b2> <b3>)))
    "We apply the induction principle."
    (have <c> _ :by ((ℕ-induct P) <a> <b>)))
  (qed <c>))  
2.1s
Clojure

And now we can finalize the commutativity proof.

(proof 'add-commute
  (assume [m ]
    (pose P := (lambda [n ] (= (+ m n) (+ n m))))
    "Case (P zero)"
    (have <a1> (= (+ m zero) m) :by (zero-identity m))
    (have <a2> (= m (+ zero m)) :by (eq/eq-sym (zero-identity-right m)))
    (have <a> (P zero) :by (eq/eq-trans <a1> <a2>))
    "Case (P (succ n))"
    (assume [n 
             Hind (P n)]
      (have <b1> (= (+ m (succ n)) (succ (+ m n))) :by (add-succ m n))
      (have <b2> (= (succ (+ m n)) (succ (+ n m))) :by (eq/eq-cong succ Hind))
      (have <b3> (= (succ (+ n m)) (+ (succ n) m))
        :by (eq/eq-sym (add-succ-swap n m)))
      (have <b> (P (succ n)) :by (eq/eq-trans* <b1> <b2> <b3>)))
    "We apply the induction principle."
    (have <c> _ :by ((ℕ-induct P) <a> <b>)))
  (qed <c>))
1.0s
Clojure

Exercises

There are other important properties to prove about addition... Your mission, if you accept it, will be to (1) find such properties, (2) prove them (in LaTTe). This message will not destroy itself at the end...

Multiplication

It is now quite easy to define the multiplication function. Indeed, we saw that addition was a kind of iteration of the successor function. Multiplication is similarly a kind of iteration of addition. This can be made a little bit more precise with the following definition :

inline_formula not implemented

Put in more conventional terms, inline_formula not implemented and inline_formula not implemented.

In LaTTe we obtain the following.

(definition times [m ]
  (q/the (ℕ-rec zero (plus m))))
0.2s
Clojure

The two defining properties can be made explicit thanks to the recursion theorem.

(defthm times-zero [m ]
  (= ((times m) zero) zero))
0.1s
Clojure
(proof 'times-zero
  (qed (p/and-elim-left (q/the-prop (ℕ-rec zero (plus m))))))
3.6s
Clojure
(defthm times-succ [m ]
  ( [n ] (= ((times m) (succ n)) ((plus m) ((times m) n))))) 
0.1s
Clojure
(proof 'times-succ
  (qed (p/and-elim-right (q/the-prop (ℕ-rec zero (plus m))))))
3.0s
Clojure

And we are now ready for the formalization of the binary operator.

(definition × [[m ] [n ]]
  ((times m) n))
0.1s
Clojure

The properties of multiplication

Multiplication is also a commutative monoid with unit inline_formula not implemented.

Exercise : prove the commutative monoid laws

  • unit element: inline_formula not implemented

  • associativity: inline_formula not implemented

  • commutativity: inline_formula not implemented

... and well ... the (multiplication) sky is the limit!

We're there : factorial from scratch!

(definition one []
  (succ zero))
0.1s
Clojure
(definition factorial [n ]
  (q/the (ℕ-rec one (times (succ n)))))
2.5s
Clojure

Hint :

inline_formula not implemented

And that's all folks!

Runtimes (1)