-- Lean has a normal functional programming language
def plus (a b : nat) : nat :=
a + b
def plus' : nat → nat → nat
| 0 b := b
| (a' + 1) b := (plus' a' b) + 1
-- But it can also write down and construct proofs of propositions
variable p : Prop
variable q : Prop
theorem t1 : p → q → p ∧ q
| pfp pfq := and.intro pfp pfq
theorem t2 : p → p ∧ p
| pfp := and.intro pfp pfp
theorem t3 : p ∧ q → p
| (and.intro pfp pfq) := pfp
theorem t4 : p → (p → q) → q
| pfp pfpimpq := pfpimpq pfp
theorem t5 : ∀ p : Prop, p → p
| p := λ pfp : p, pfp
theorem t6 : p → ¬ ¬ p
| pfp := λ pfnp, pfnp pfp
-- Propositions aren't just abstract;
-- you can write down propositions about program values
variable n : nat
#check n = n
#check 2 = 2
#check 2 = 3
theorem eq22 : 2 = 2 := rfl
--def eq23 : 2 = 3 := rfl
inductive option' (A : Type) : Type
| none : option'
| some : A → option'
def sqrt_of (n : nat) := { k : nat // k * k = n }
-- Lean can do some (limited) computation when
-- proving propositions
def eq1p1is2 : 1 + 1 = 2 := rfl
def sqrtof9 : sqrt_of 9 := subtype.mk 3 rfl
def even (n : nat) := ∃ k, 2 * k = n
lemma t6' : even 4 := exists.intro 2 rfl
-- One nice thing you can do is define recursive propositions,
-- which dafny calls "inductive"
inductive even' : nat → Prop
| even_O : even' 0
| even_SS : ∀ {n}, even' n → even' (n + 2)
def t7 : even' 4 :=
even'.even_SS (even'.even_SS even'.even_O)
-- Recursive propositions are usually constructed by recursion:
def double : nat → nat
| 0 := 0
| (n + 1) := (double n) + 2
theorem double_even' : ∀ n, even' (double n)
| 0 :=
-- even' (double 0)
-- even' 0
even'.even_O
| (n + 1) :=
-- even' (double (n + 1))
-- even' ((double n) + 2)
-- even' (double n)
even'.even_SS (double_even' n)
-- In class, I got this one wrong,
-- and attempted to do the recursion over n:
-- n + 0, 0 + m, and (n + 1) + (m + 1) cases.
-- The problem with this approach is that
-- in the (n + 1) + (m + 1) case, we can't
-- recurse to the n + m case because
-- *n and m aren't necessarily even!*
-- The solution is what I had planned to
-- demonstrate with this example all along:
-- that instead of recursing on n and m
-- we can recurse on the *proofs* that
-- n and m are even:
lemma plus_0 : ∀ n : nat, 0 + n = n
| 0 :=
rfl
| (n + 1) :=
calc 0 + (n + 1)
= (0 + n) + 1 : rfl
... = n + 1 : by rw plus_0
lemma plus_2 : ∀ n m : nat, (n + 2) + m = (n + m) + 2
| n 0 := rfl
| n (m + 1) :=
calc (n + 2) + (m + 1)
= ((n + 2) + m) + 1 : rfl
... = (n + m + 2) + 1 : by rw plus_2
... = (n + (m + 1)) + 2 : rfl
theorem plus_even
: ∀ n m, even' n → even' m → even' (n + m)
| n 0 pfne (even'.even_O) :=
pfne
| 0 m (even'.even_O) pfme :=
eq.subst (eq.symm (plus_0 m)) pfme
-- In this case, we now know that the proofs pfne and pfme
-- are both even_SS proofs, which implies that n is at least 2
-- and m is at least 2 as well
| (n + 2) (m + 2) (even'.even_SS pfne) (even'.even_SS pfme) :=
let pf1 : even' (n + m) := plus_even n m pfne pfme in
let pf2 : even' (n + m + 2) := even'.even_SS pf1 in
let pf3 : even' (n + 2 + m) := eq.subst (eq.symm (plus_2 n m)) pf2 in
even'.even_SS pf3
-- This code computes the square root of a natural number
-- (using a naive algorithm: test every number n ... 0),
-- but it also constructs a proof that it is a square root!
-- This also means that it has to return a "nullable" value
-- since not every (natural) number has a (natural) square root
def sqrt_helper (n : nat) : nat → option (sqrt_of n)
| 0 :=
dite (n = 0)
(λ pfn0, option.some (subtype.mk 0 (eq.symm pfn0)))
(λ pfnn0, option.none)
| (k + 1) :=
dite ((k + 1) * (k + 1) = n)
(λ pfkkn, option.some (subtype.mk (k + 1) pfkkn))
(λ pfkknn, sqrt_helper k)
def sqrt (n : nat) : option (sqrt_of n) :=
sqrt_helper n n