import tactic
attribute [pp_nodot] nat.succ -- used to make sure that `n.succ` appears as `succ n` (`pp` stands for *pretty printer*).
open nat
∀ x, P x
)Statements of the form ∀ (n : ℕ), P n
, where
P : ℕ → Prop
is a sequence of propositions, are
common in mathematics (the symbol ∀
is obtained by typing
\forall
or even simply \fo
).
Similarly, we could have a statement of the form
∀ (n : ℤ), P n
. For instance, the fact that the square of
an integer n
is non-negative, can be written as follows
∀ (n : ℤ), n^2 ≥ 0
and this, too, is a statement that Lean understands.
#check ∀ (n : ℤ), n^2 ≥ 0
In fact, this result is proved under a much more general form in
mathlib
, the mathematical library of Lean. It is recorded
there as a function sq_nonneg : R → Prop
which sends a term
a
of type R
(where R
is an
ordered ring) to a proof of the proposition a^2 ≥ 0
.
#check @sq_nonneg
To understand better how the function sq_nonneg
works,
let us use it here to construct a more specific function called
squares_are_non_neg
, which sends an integer n
to a proof of n^2 ≥ 0
.
def squares_are_non_neg : ∀ (n : ℤ), n^2 ≥ 0 :=
begin
intro n,
exact sq_nonneg n,
end
#check squares_are_non_neg
#check squares_are_non_neg 3
This last #check
gives us a hint of how a such a
so-called universal statement is defined.
As we see, squares_are_non_neg 3
is a proof of the
proposition 3^2 ≥ 0
.
So squares_are_non_neg
is a function from ℤ
to Prop
that sends n : ℤ
to a proof of
n^2 ≥ 0 : Prop
. In other words,
squares_are_non_neg n
is a term of type
squares_are_non_neg n
, a type that itself depends
on n
.
Such a type is called a dependent type. We used
dependent types here in order to define a statement involving the
universal quantifier ∀
.
To define the function squares_are_non_neg
without using
the symbol ∀
, we proceed as follows.
def squares_are_non_neg_bis (n : ℤ) : n^2 ≥ 0 := --sq_nonneg n
begin
exact sq_nonneg n,
end
This syntax makes it clearer that
squares_are_non_neg_bis n
is a term of type
n^2 ≥ 0
, which is seen to depend on n
.
Note that, after the initial declaration, the rest of the definition
of squares_are_non_neg_bis
is the same as for
squares_are_non_neg
, except that we no longer need to
introduce n
when going into tactic mode.
If we check the type of squares_are_non_neg_bis
, we find
the exact same thing as for squares_are_non_neg
.
#check squares_are_non_neg
#check squares_are_non_neg_bis
Write a proof of ∀ (n : ℤ), n^2 ≥ 0
without using the
function sq_nonneg
.
Hint: using by_cases n ≥ 0
, separate the cases
n ≥ 0
and n ≤ 0
. When n ≥ 0
, then
find in mathlib
a proof of
∀ a b, (a ≥ 0 ∧ b ≥ 0) → ab ≥ 0
(or something similar) and
apply it with a = b = n
. When n ≤ 0
, argue
that n^2 = (-n)^2
and that -n ≥ 0
, then repeat
the previous argument.
Here is something to get you started. Try to fill in the
sorry
’s. The solution is available in the Solutions
folder.
example : ∀ (n : ℤ), n^2 ≥ 0 :=
begin
intro n,
by_cases n ≥ 0,
{
rw sq,
apply mul_nonneg h,
sorry,
},
{
simp at h,
rw ← neg_sq,
rw sq,
sorry,
},
end
Let us give an example of a function defined recursively.
A natural number is either 0
or of the form
n+1
for some already defined natural number n
.
So, to define a function on the natural numbers, it suffices to define
it in these two cases.
The syntax to do that is as follows. In this example, the definition
of fac (n+1)
uses the already defined fac n
,
making the function recursive.
def fac : ℕ → ℕ
| 0 := 1
| (n + 1) := (n + 1) * fac n
#check fac
#eval fac 5
We can intoduce a commonly used notation for the factorial function.
The :10000
is optional in principle but here it is actually
necessary (the number 10000
parameterises the “strength”
with which the notation is to be “upheld”).
notation n `!`:10000 := fac n
We will prove below that ∀ (n : ℕ), fac n > 0
. Given
the way the function fac
is defined, it makes sense to
prove it by induction, using the induction
tactic.
This can be done directly, which is a good exercise, or we can first
define an induction principle function, that enables us with
the readability of induction as it is performed later in the proof of
∀ (n : ℕ), fac n > 0
.
Indeed, in the definition of the function
induction_pple
, the final goal of the induction appears as
P(succ k)
, while in the proof of
∀ (n : ℕ), fac n > 0
, the successor function
succ
does not appear at all, making everything closer to
the usual mathematical notation.
def induction_pple {P : ℕ → Prop} (p0 : P 0) (ih : ∀ (k : ℕ), P k → P (k + 1)) : ∀ (n : ℕ), P n :=
begin
intro n,
induction n with k hk,
exact p0,
exact ih k hk,
end
You can think of the function induction_pple
as a kind
of macro, that we program once and for all and then use in our code.
def fac_pos : ∀ (n : ℕ), n! > 0 :=
begin
apply induction_pple,
{
-- rw fac,
exact zero_lt_one,
},
{
intro k,
intro h,
rw fac, -- unfold fac (also works)
apply mul_pos,
apply succ_pos,
exact h,
},
end
#check fac_pos
Define a function S : ℕ → ℚ
sending n
to
0 + 1 + 2 + ... + n
, then prove by induction that
∀ n : ℕ, S n = n * (n+1) / 2
.