Zulip Chat Archive
Stream: Lean for teaching
Topic: A useful example of "induction ... using ..." (?)
Frederic Peschanski (Dec 12 2023 at 23:33):
I don't know if it is a correct place for sharing "course tips" ... sorry if it is not.
In the context of a course "specification and verification of programs" I am giving since 2021 using Lean4 (replacing Coq ...) at Sorbonne University (for CS students), I was looking for a good example of when the tactic induction ... using ...
would be useful. Triggered by this stack overflow question, I ended up with what follows.
It is a little bit longer than I expected (I did not try to make it as concise at what would be needed), but it might be useful if you want to illustrate this feature of the induction tactic.
I am of course interested by other similar examples ...
-- I use a separate LList type to be self-contained
inductive LList (α : Type u) where
| nil : LList α
| cons (x:α) (xs:LList α): LList α
deriving Repr
open LList
def lappend (xs ys : LList α) :=
match xs with
| nil => ys
| cons x xs => LList.cons x (lappend xs ys)
theorem lappend_nil (xs : LList α):
lappend xs nil = xs :=
by
induction xs
case nil => simp [lappend]
case cons x xs Hind =>
simp [lappend, Hind]
theorem lappend_assoc (xs ys zs : LList α):
lappend xs (lappend ys zs) = lappend (lappend xs ys) zs :=
by
induction xs
case nil => simp [lappend]
case cons x xs Hind =>
simp [lappend, Hind]
def snoc (x : α) (xs : LList α) := lappend xs (cons x nil)
def rev (xs : LList α) :=
match xs with
| nil => nil
| cons x xs => snoc x (rev xs)
theorem rev_snoc (x : α) (xs : LList α):
rev (snoc x xs) = cons x (rev xs) :=
by
induction xs
case nil => simp [rev, lappend, snoc]
case cons y xs Hind =>
simp [rev, lappend, snoc] at Hind
simp [Hind, lappend, snoc, rev]
theorem rev_rev (xs : LList α):
rev (rev xs) = xs :=
by
induction xs
case nil => simp [rev]
case cons x xs Hind =>
simp [rev, snoc]
show rev (snoc x (rev xs)) = cons x xs
simp [rev_snoc, Hind]
-- a first induction principle, easy to prove but unusable because
-- the conclusion is not in a `(motive <var>)` form ...
theorem list_ind_rev_snoc (P : LList α → Prop):
P nil
→ (∀ x xs, P xs → P (snoc x xs))
→ ∀ xs : LList α, P (rev xs) :=
by
intros Hnil Hsnoc xs
induction xs
case nil => assumption
case cons x xs Hind =>
simp [rev]
apply Hsnoc
assumption
-- this is ugly but it is a valid induction principle now...
theorem list_ind_rev_rev_snoc (P : LList α → Prop):
P nil
→ (∀ x xs, P (rev xs) → P (snoc x (rev xs)))
→ ∀ xs : LList α, P xs :=
by
intros Hnil Hsnoc xs
have Hrev: rev (rev xs) = xs := by apply rev_rev
rw [←Hrev]
apply list_ind_rev_snoc
· assumption
· intros y ys HP
clear Hrev
have Hrev: rev (rev ys) = ys := by apply rev_rev
rw [←Hrev]
apply Hsnoc
· simp [Hrev, HP]
-- this one is trivial, but it's the key I was missing at first
theorem list_ind_snoc_lemma (P : LList α → Prop):
(∀ x xs, P xs → P (snoc x xs))
→ (∀ x xs, P (rev xs) → P (snoc x (rev xs))) :=
by
intros H1 x xs
apply H1
-- this is what I wanted first ... but I ended up going the "rev" way
theorem list_ind_snoc (P : LList α → Prop):
P nil
→ (∀ x xs, P xs → P (snoc x xs))
→ ∀ xs : LList α, P xs :=
by
intros Hnil Hsnoc'
have Hsnoc: (∀ x xs, P (rev xs) → P (snoc x (rev xs))) := by
apply list_ind_snoc_lemma ; assumption
apply list_ind_rev_rev_snoc <;> assumption
-- this is where thing become interesting
theorem lappend_snoc (xs ys : LList α):
∀ y : α, lappend xs (snoc y ys) = snoc y (lappend xs ys) :=
by
induction ys using list_ind_snoc
case a => simp [lappend_nil, snoc, lappend]
case a x ys Hind =>
intro y
simp [Hind, snoc, lappend_assoc]
-- and there you are stack overflow !
theorem rev_lappend (xs ys : LList α):
rev (lappend xs ys) = lappend (rev ys) (rev xs) :=
by
induction ys using list_ind_snoc
case a => simp [rev, lappend, lappend_nil]
case a x ys Hind =>
simp [rev_snoc, lappend, ←Hind, lappend_snoc]
François G. Dorais (Dec 12 2023 at 23:45):
Not about lists and perhaps not as interesting but there are a few examples in Std: https://github.com/leanprover/std4/blob/483fd2846f9fe5107011ece0cc3d8d88af1a8603/Std/Data/Nat/Lemmas.lean#L704C1-L727C1
François G. Dorais (Dec 12 2023 at 23:49):
This type of diagonal induction is also useful for proving theorems about zip
.
Yury G. Kudryashov (Dec 13 2023 at 04:29):
We have several induction principles for docs#Finset. The default "destruct into a Multiset
and a proof of NoDup
" is the least useful one.
Yury G. Kudryashov (Dec 13 2023 at 04:32):
Also, you can add an induction principle
theorem cauchy_induction (p : Nat → Prop) (one : p 1) (mul_two : ∀ n, p n → p (2 * n)) (lt : ∀ m n, m < n → p n → p m) :
∀ n, p n := sorry -- get `p (2 ^ (n + 1))` from `one` and `mul_two`, then use `lt`
then use it to prove the AM-GM inequality: the mul_two
step is basically 2ab ≤ a²+b²
and the lt
step can be done by setting the last n - m
numbers to the arithmetic mean of the first m
numbers.
Frederic Peschanski (Dec 13 2023 at 08:10):
These are excellent examples ... thank you !
Frederic Peschanski (Dec 13 2023 at 08:18):
Looking at recDiagAux
I get it that it is better to give names to assumptions, e.g. :
theorem list_ind_snoc (P : LList α → Prop)
(nil : P nil)
(ind : (∀ x xs, P xs → P (snoc x xs))):
∀ xs : LList α, P xs :=
by
...
so that the case names are e.g. nil
and ind
(inductive...)
Mario Carneiro (Dec 13 2023 at 08:25):
it's also better to use the names of existing functions when possible, so that hover works
Mario Carneiro (Dec 13 2023 at 08:25):
so in that example I would suggest using the names nil
and snoc
Frederic Peschanski (Dec 13 2023 at 08:43):
Mario Carneiro said:
so in that example I would suggest using the names
nil
andsnoc
When I do that, there seems to be a name collision maybe because snoc
is a def
and not a constructor ?
α: Type u_1
P: LList α → Prop
nil: P LList.nil
snoc: ∀ (x : α) (xs : LList α), P xs → P (_root_.snoc x xs)
⊢ ∀ (xs : LList α), P xs
(and a type error ...)
I don't know exactly what happens here...
Mario Carneiro (Dec 13 2023 at 08:56):
yes, that is expected
Mario Carneiro (Dec 13 2023 at 08:56):
the type error is not
Mario Carneiro (Dec 13 2023 at 08:56):
I think it only works if snoc
is a definition in the LList
namespace
Mario Carneiro (Dec 13 2023 at 08:56):
it's a bit weird that it is not here
Frederic Peschanski (Dec 13 2023 at 10:53):
Mario Carneiro said:
I think it only works if
snoc
is a definition in theLList
namespace
This works, thanks !
namespace LList
def snoc (x : α) (xs : LList α) := lappend xs (cons x nil)
end LList
However, I need to do that :
theorem list_ind_snoc (P : LList α → Prop)
(nil : P nil)
(snoc : (∀ x xs, P xs → P (snoc x xs))):
∀ xs : LList α, P xs :=
by
have Hsnoc: (∀ x xs, P (rev xs) → P (LList.snoc x (rev xs))) := by
apply list_ind_snoc_lemma ; assumption
apply list_ind_rev_rev_snoc <;> assumption
(remark the LList.snoc
otherwise I get the same type error ... probably because the namespace is opened...)
Mario Carneiro (Dec 13 2023 at 10:54):
I don't know what this is being compared to, or what the type error was
Mario Carneiro (Dec 13 2023 at 10:55):
You probably want list_ind_snoc
to also be in the LList
namespace, in which case you won't need to use LList.snoc
in the proof
Mario Carneiro (Dec 13 2023 at 10:56):
you can also use .snoc
to avoid writing the namespace even if it's not open
Last updated: Dec 20 2023 at 11:08 UTC