PRFA.live_coding3
Live coding session for the third lecture of MPRI PRFA (2-7-2)
Dependent functions and type universes
Definition dependent (b : bool) : if b then nat else bool :=
match b with true ⇒ 0 | false ⇒ true end.
match b with true ⇒ 0 | false ⇒ true end.
Another important part of the theory is the notion of universes.
Essentially Type and Prop are universes. They contain respectively the
types and propositions (as the names suggest).
Check Type.
Above you see that Type itself has type Type. It makes sense, after all
Type is indeed a Type. However, assuming Type : Type actually leads to
inconistencies (proofs of False) so instead, we have a hierarchy of
universes: basically Type₀ : Type₁ : Type₂ ….
You can ask Coq to show you universe levels by using the following command.
If you are using VSCoq Legacy, it will not work and instead you need to use
the command palette and choose "Coq: Display options" and then
"Universe levels".
Do exactly the same to switch it back.
Set Printing Universes.
Check Type.
Unset Printing Universes.
This is not very easy to read but essentially it says that Typeᵢ lives
in Typeᵢ₊₁.
The fact that the universe levels are not displayed and inferred by the
system is sometimes called: typical ambiguity.
Even while hidden they still play an important role.
If for instance we alias Type under some name T, we will run into an
issue with the following definition.
Check Prop.
Now let us use Print to see what the proof is to Coq.
Print even_four.
Now the proof of even 4 we write directly as follows:
Forall, implication
Goal ∀ P Q, P → (P → Q) → Q.
Proof.
refine (fun P Q x f ⇒ _). (* Basically here we do intros P Q x f *)
refine (f _). (* Now apply f *)
refine x. (* Now apply x *)
Qed.
Γ ⊢ A → B
Γ ⊢ λ (x : A). t : A → B
Γ ⊢ B
Γ ⊢ f u : B
Γ ⊢ λ (x : A). t : ∀ (x : A). B
Γ ⊢ f u : Bx := u
Goal ∀ (f : ∀ n, even n), even 1.
Proof.
refine (fun f ⇒ f 1).
(* f 1 has type even 1, the n was substitued by 1. *)
Qed.
Let us see again proof terms in action and how it compares to tactics.
Lemma even_plus_four : ∀ (n : nat) (H : even n), even (4 + n).
Proof.
intros n H.
apply evenS.
apply evenS.
apply H.
Qed.
Lemma even_plus_four_refine : ∀ (n : nat) (H : even n), even (4 + n).
Proof.
refine (fun n H ⇒ evenS (evenS _)).
refine H.
Qed.
We can just do it in direct style.
And, or, exists
Module Show_Definitions.
Inductive False := .
Inductive True := I.
Notation "~ P" := (P → False).
Inductive and (P Q : Prop) : Prop :=
| conj : P → Q → and P Q.
Inductive or (P Q : Prop) : Prop :=
| or_introl : P → or P Q
| or_intror : Q → or P Q.
End Show_Definitions.
You can also get them using Print.
Print False.
Print True.
Print and.
Print or.
Print True.
Print and.
Print or.
To eliminate a proof of False, one can just match on it and provide the
exactly zero cases that are needed.
The rest should be easier to understand.
Definition elim_and : ∀ (X Y Z : Prop), X ∧ Y → (X → Y → Z) → Z :=
fun X Y Z a e ⇒
match a with
| conj x y ⇒ e x y
end.
Definition elim_or : ∀ (X Y Z : Prop), X ∨ Y → (X → Z) → (Y → Z) → Z :=
fun X Y Z a e1 e2 ⇒
match a with
| or_introl x ⇒ e1 x
| or_intror y ⇒ e2 y
end.
Using tactics to build definitions.
Fixpoint add (n m : nat) {struct n} : nat.
Proof.
destruct n.
- apply m.
- apply S. apply add. apply n. apply m.
Defined.
Modulo some simplication, we get the same thing as what we would have
written by hand.
Proofs using terms can be quite short.
Goal ∀ X (p q : X → Prop),
(∀ x, p x ↔ q x) →
(∀ x, q x) →
∀ x, p x.
Proof.
intros X p q f g x.
refine (match f x with conj _ h ⇒ _ end).
exact (h (g x)).
Show Proof.
Qed.
(∀ x, p x ↔ q x) →
(∀ x, q x) →
∀ x, p x.
Proof.
intros X p q f g x.
refine (match f x with conj _ h ⇒ _ end).
exact (h (g x)).
Show Proof.
Qed.
Guard condition
Unset Guard Checking. (* This deactivates the termination check. *)
False is simply proven via a loop!
We get back a proof of False by applying the function f.
Goal False.
Proof.
pose (f := fun '(C g) ⇒ g (C g)). (* The tick notation does pattern matching *)
exact (f (C f)).
Qed.
Set Positivity Checking.
Recursors
Fixpoint nat_rect (P : nat → Type) (z : P 0)
(s : ∀ (n : nat), P n → P (S n)) (n : nat) : P n :=
match n with
| 0 ⇒ z
| S n ⇒ s n (nat_rect P z s n)
end.
Fixpoint list_rect {X} (P : list X → Type) (n : P nil)
(c : ∀ (x : X) (l : list X), P l → P (cons x l)) (l : list X) : P l :=
match l with
| nil ⇒ n
| cons x l ⇒ c x l (list_rect P n c l)
end.
Reduction
Now simpl does much more than β so if we want to only perform β we do:
Unfolding definitions
There are several ways to unfold definitions.
The first one is with the unfold strategy.
unfold foo will unfold the constant foo.
Another one is to perform δ-reduction which simply replaces names by the
corresponding definitions. A list of names can be provided but is optional.
If no name is provided then all possible δ-reductions are performed.
To use delta we switch to cbv which stands for call-by-value.
Note: δ is like definition.
Definition foo := 2 + 9.
Definition bar := let x := 10 in x × x.
Definition baz := foo + bar.
Eval unfold foo in foo + foo.
Eval cbv delta [foo bar] in foo + bar + baz.
Eval cbv delta in foo + bar + baz.
Goal 2 + 1 = 3.
Proof.
cbv delta.
cbn fix. (* This performs one unfolding of the fix. *)
(* It's still there in the recursive branch! *)
cbn match. (* Now we reduce to the right branch. *)
cbn fix. cbn match. (* One more step. *)
cbn iota. (* Or we use ι-reduction which does it all at once. *)
Abort.
Control of unfoldings
Somethimes we want to simplify an expression but only focusing on some parts.
Say in the expression (S n + m) - m from last week where we only wanted
to reduce the + part. We showed simpl add but there are other ways to do
it.
Goal ∀ n m, (S n + m) - m = n.
Proof.
intros n m.
cbn [ "+" ]. (* Notice how we can use the notation directly! *)
Abort.
Sometimes we'd rather provide a list of exceptions not to be unfolded:
There are more strategies like lazy, hnf, or vm_compute…
More detail in the reference manual:
https://coq.inria.fr/doc/v8.18/refman/proofs/writing-proofs/equality.htmlapplying-conversion-rules
*
Here is a demo of examples of proof terms.
In particular we show you cases where the proof term is much shorter than
using tactics.
Section Demo.
Variables X Y Z: Prop.
Check fun x:X ⇒ x.
Check fun (x: X) (y: Y) ⇒ x.
Check fun (x: X) (y: Y) ⇒ y.
Check fun (f: X → Y → Z) y x ⇒ f x y.
Check fun h: X ∧ Y ⇒ match h with conj x y ⇒ x end.
Check fun h: X ∧ Y ⇒ match h with conj x y ⇒ y end.
Check fun h: X ∧ Y ⇒ match h with conj x y ⇒ conj y x end.
Check
fun h: (X ∧ Y) ∧ Z ⇒
match h with
conj (conj x y) z ⇒ conj x (conj y z)
end.
Variables X Y Z: Prop.
Check fun x:X ⇒ x.
Check fun (x: X) (y: Y) ⇒ x.
Check fun (x: X) (y: Y) ⇒ y.
Check fun (f: X → Y → Z) y x ⇒ f x y.
Check fun h: X ∧ Y ⇒ match h with conj x y ⇒ x end.
Check fun h: X ∧ Y ⇒ match h with conj x y ⇒ y end.
Check fun h: X ∧ Y ⇒ match h with conj x y ⇒ conj y x end.
Check
fun h: (X ∧ Y) ∧ Z ⇒
match h with
conj (conj x y) z ⇒ conj x (conj y z)
end.
Note implicit argument overloading of or_introl and or_intror
Check fun h: X ∨ Y ⇒ match h with
or_introl x ⇒ or_intror Y x
| or_intror y ⇒ or_introl X y
end.
Check fun h: (X ∨ Y) ∨ Z ⇒ match h with
or_introl (or_introl x) ⇒ or_introl (Y ∨ Z) x
| or_introl (or_intror y) ⇒ or_intror (or_introl y)
| or_intror z ⇒ or_intror (or_intror z)
end.
Check conj
(fun h: X ∧ Y ⇒ match h with conj x y ⇒ conj y x end)
(fun h: Y ∧ X ⇒ match h with conj y x ⇒ conj x y end).
Check fun h: False ⇒ match h return X with end. (* exfalso quodlibet *)
Check fun x (f: ¬X) ⇒ f x.
Check fun x (f: ¬X) ⇒ elim_False Y (f x).
Check fun x (f: ¬X) ⇒ match f x return Y with end.
Check fun (f: X → ¬X) g ⇒ let x := g (fun x ⇒ f x x) in f x x.
Fact Russell :
~(X ↔ ¬X).
Proof.
intros [f g].
assert (x: X).
- apply g. intros x. exact (f x x).
- exact (f x x).
Show Proof.
Qed.
Goal ~(X ↔ ¬X).
Proof.
refine (fun a ⇒ match a with conj f g ⇒ _ end).
refine (let x:X := _ in f x x).
refine (g (fun x ⇒ _)).
exact (f x x).
Show Proof.
Qed.
End Demo.
ADVANCED
Mutual inductive types
Inductive ntree (A : Type) :=
| nnode : A → nforest A → ntree A
with nforest (A : Type) :=
| nnil : nforest A
| ncons : ntree A → nforest A → nforest A.
You can then define mutual definitions over such types by using Fixpoint
and the with keyword.
Fixpoint count {A} (t : ntree A) {struct t} : nat :=
match t with
| nnode _ a l ⇒ 1 + count_list l
end
with count_list {A} (l : nforest A) {struct l} : nat :=
match l with
| nnil _ ⇒ 0
| ncons _ t tl ⇒ count t + count_list tl
end.
Nested inductive types
Inductive tree :=
| N (ts : list tree).
Definition empty_tree :=
N [].
Definition some_tree :=
N [ empty_tree ; N [ empty_tree ; empty_tree ] ].