Theorem review2: ∀b, (orb true b) = true. Theorem review3: ∀b, (orb b true) = true.
Whether or not it can be just simpl. depending on the definition of orb.
In Proof Engineering, we probably won’t need to include review2 but need to include review3 in library.
Why we have simpl. but not refl. ?
Proving 0 is a “neutral element” for + (additive identity)
Proving 0 + n = n
1 2 3
Theorem plus_O_n : forall n : nat, 0 + n = n. Proof. intros n. simpl. reflexivity. Qed.
This can be simply proved by simplication bcuz the definition of + is defined by pattern matching against 1st operand:
1 2 3 4 5
Fixpoint plus (n : nat) (m : nat) : nat := match n with | O ⇒ m | S n' ⇒ S (plus n' m) end.
We can observe that if n is 0(O), no matter m is, it returns m as is.
Proving n + 0 = n
1st try: Simplication
1 2 3 4 5
Theorem plus_O_n_1 : forall n : nat, n + 0 = n. Proof. intros n. simpl. (* Does nothing! *) Abort.
This cannot be proved by simplication bcuz n is unknown so unfold the definition + won’t be able to simplify anything.
2nd try: Case Analysis
1 2 3 4 5 6 7 8 9
Theorem plus_n_O_2 : ∀n:nat, n = n + 0. Proof. intros n. destruct n as [| n'] eqn:E. - (* n = 0 *) reflexivity. (* so far so good... *) - (* n = S n' *) simpl. (* ...but here we are stuck again *) Abort.
Our 2nd try is to use case analysis (destruct), but the proof stucks in inductive case since n can be infinitely large (destructed)
Induction to the resucue
To prove interesting facts about numbers, lists, and other inductively defined sets, we usually need a more powerful reasoning principle: induction.
Princeple of induction over natural numbers (i.e. mathematical induction)
1
P(0); ∀n' P(n') → P(S n') ====> P(n)
In Coq, like destruct, induction break P(n) into 2 subgoals:
1 2 3 4 5
Theorem plus_n_O : ∀n:nat, n = n + 0. Proof. intros n. induction n as [| n' IHn']. - (* n = 0 *)reflexivity. - (* n = S n' *)simpl. rewrite <- IHn'. reflexivity. Qed.
Proving n - n = 0
1 2 3 4 5 6 7 8 9
Theorem minus_diag : ∀n, minus n n = 0. Proof. (* WORKED IN CLASS *) intros n. induction n as [| n' IHn']. - (* n = 0 *) simpl. reflexivity. - (* n = S n' *) simpl. rewrite → IHn'. reflexivity. Qed
Noticed that the definition of minus:
1 2 3 4 5 6
Fixpoint minus (n m:nat) : nat := match n, m with | O , _ => O | S_ , O => n | S n', S m' => minus n' m' end.
rewrite
rewrite would do a (DFS) preorder traversal in the syntax tree.
Q: Why name inductive? A: Inductive means building things bottom-up, it doesn’t have to self-referencial (recursive) (see below induction on lists as well.)
The ability of quosiquotation using Notation is awesome:
1 2 3
Notation"x :: l" := (cons x l) (at level 60, right associativity). Notation"[ ]" := nil. Notation"[ x ; .. ; y ]" := (cons x .. (cons y nil) ..).
It’s exactly like OCaml, even for ;, at level 60 means it’s tightly than + at level 50 .
1
Notation"x ++ y" := (app x y) (right associativity, at level 60).
Instead of SML/OCaml’s @, Coq chooses Haskell’s ++.
hd with default
Coq function (for some reason) has to be total, so hd require a default value as 1st argument:
1 2 3 4 5
Definition hd (default:nat) (l:natlist) : nat := match l with | nil ⇒ default | h :: t ⇒ h end.
Induction on Lists.
The definition of inductive defined set
Each Inductive declaration defines a set of data values that can be built up using the declared constructors:
a boolean can be either true or false;
a number can be either O or S applied to another number;
a list can be either nil or cons applied to a number and a list.
The reverse: reasoning inductive defined sets
Moreover, applications of the declared constructors to one another are the only possible shapes that elements of an inductively defined set can have, and this fact directly gives rise to a way of reasoning about inductively defined sets:
a number is either O or else it is S applied to some smaller number;
a list is either nil or else it is cons applied to some number and some smaller list;
Reasoning lists
if we have in mind some proposition P that mentions a list l and we want to argue that P holds for all lists, we can reason as follows
First, show that P is true of l when l is nil.
Then show that P is true of l when l is cons n l' for some number n and some smaller list l', assuming that P is true for l'.
Search
1
Search rev (* list all theorems of [rev] *)
Coq Conditionals (if then else)
1 2 3 4 5 6
Fixpoint nth_error' (l:natlist) (n:nat) : natoption := match l with | nil ⇒ None | a :: l' ⇒ if n =? O then Some a else nth_error' l' (pred n) end.
One small generalization: since the boolean type in Coq is not built-in. Coq actually supports conditional expr over anyinductive defined typewith two constructors. First constructor is considered true and false for second.
The critical new ideas are polymorphism (abstracting functions over the types of the data they manipulate) and higher-order functions (treating functions as data).
Polymorphism
Until today, We were living in the monomorphic world of Coq. So if we want a list, we have to define it for each type:
But of course Coq supports polymorphic type. So we can abstract things over type
1 2 3 4 5 6
Inductive list (X:Type) : Type := | nil | cons (x : X) (l : list X).
Check list. (* ===> list : Type -> Type *)
Recall from PLT course, this is exacly Parametric Polymorphism and it’s SystemFω. the list here is a type-level small lambda, or type operators
Another things I’d love to mention is the concrete syntax of list X, it didn’t choose the SML/OCaml order but the Haskell order.
Q1. What’s the type of nil and cons?
Both having forall type
1 2 3 4
Check nil. (* ===> nil : forall X : Type, list X *) Check cons. (* ===> nil : forall X : Type, X -> list X -> list X *)
Q2. What’s the type of list nat? Why not Type but weird Set?
1 2 3 4 5 6 7 8
Check nat. (* ===> nat : Set *) Check list nat. (* ===> list nat : Set *) CheckSet. (* ===> Set: Type *) CheckType. (* ===> Type: Type *)
1
Check (cons nat 2 (cons nat 1 (nil nat))).
Polymorphic Functions
we can make polymorphic versions of list-processing function:
Btw, Pierce follows the TAPL convention where type is written in capital letter but not greek letter, less clear in first look but better for typing in real programming.
1 2 3 4 5
Fixpointrepeat (X : Type) (x : X) (count : nat) : list X := match count with | 0 ⇒ nil X | S count' ⇒ cons X x (repeat X x count') end.
This is SystemF.
1 2
Check repeat. (* ===> repeat : forall X : Type, X -> nat -> list X *)
Slide QA
ill-typed
forall X : Type, X -> nat -> list X
list nat
Type Argument Inference
X must be a Type since nil expects an Type as its first argument.
1 2 3 4 5 6 7 8
Fixpointrepeat' X x count : list X := (* return type [:list X] can be omitted as well *) match count with | 0 ⇒ nil X | S count' ⇒ cons X x (repeat' X x count') end.
Checkrepeat'. (* ===> forall X : Type, X -> nat -> list X *)
Type Argument Synthesis
We can write _ (hole) in place of X and Coq will try to unify all local information.
1 2 3 4 5 6 7 8
Fixpointrepeat'' X x count : list X := match count with | 0 ⇒ nil _ | S count' ⇒ cons _ x (repeat'' _ x count') end.
repeat' X x count : list X := repeat' (X : _) (x : _) (count : _) : list X :=
Implicit Arguments
Using Arguments directives to tell if an argument need to be implicit (i.e. omitted and always to infer) or not.
Implicitly convert to _ (synthesis) by frontend.
1 2 3
Arguments nil {X}. Arguments cons {X} __. (* data constructor usually don't specify the name *) Argumentsrepeat {X} x count. (* fun definition usually do *)
The even more convenient syntax is that we can declare them right in our function definition. Just surrounding them with curly braces.
1 2 3 4 5
Fixpointrepeat''' {X : Type} (x : X) (count : nat) : list X := match count with | 0 ⇒ nil | S count' ⇒ cons x (repeat''' x count') end.
Doing this will make X implicit for even list', the type constructor itself…
Other Polymorphic List functions
No difference but add implicit type argument {X : Type}.
Supplying Type Arguments Explicitly
1 2 3 4 5 6
Fail Definition mynil := nil.
Definition mynil : list nat := nil.
Check @nil. (* ===> @nil : forall X : Type, list X *) Definition mynil' := @nil nat.
First thought: Existential Second thought: A wait to be unified Universal. (after being implicit and require inference)
1 2 3 4 5
Check nil.
nil : list ?X where ?X : [ |- Type]
List notation
1 2 3 4 5 6
Notation"x :: y" := (cons x y) (at level 60, right associativity). Notation"[ ]" := nil. Notation"[ x ; .. ; y ]" := (cons x .. (cons y []) ..). Notation"x ++ y" := (app x y) (at level 60, right associativity).
Same with before thanks to the implicit argument
Slide Q&A 2
we use ; not ,!!
list nat
ill-typed
ill-typed
list (list nat)
list (list nat) (tricky in first look)
list bool
ill-typed
ill-typed
Poly Pair
1 2 3 4 5 6
Inductive prod (X Y : Type) : Type := | pair (x : X) (y : Y). Arguments pair {X} {Y} __. (* omit two type var **)
Notation"( x , y )" := (pair x y). Notation"X * Y" := (prod X Y) : type_scope. (* only be used when parsing type, avoids clashing with multiplication *)
Be careful of (X,Y) and X*Y. Coq pick the ML way, not haskell way.
Combine or Zip
1 2 3 4 5 6 7
Fixpoint combine {X Y : Type} (lx : list X) (ly : list Y) : list (X*Y) := match lx, ly with | [], _ ⇒ [] | _, [] ⇒ [] | x :: tx, y :: ty ⇒ (x, y) :: (combine tx ty) end.
Guess type?
1 2 3 4 5 6 7 8 9 10 11 12
Check @combine. @combine : forall X Y : Type, list X -> list Y -> list (X * Y)
(* A special form of `forall`? *) Check combine. combine : list ?X -> list ?Y -> list (?X * ?Y) where ?X : [ |- Type] ?Y : [ |- Type]
Poly Option
1 2 3 4 5 6 7 8 9 10 11 12 13 14
Inductive option (X:Type) : Type := | Some (x : X) | None.
Arguments Some {X} _. Arguments None {X}.
(* find nth element if exist, None otherwise *) Fixpoint nth_error {X : Type} (l : list X) (n : nat) : option X := match l with | [] ⇒ None | a :: l' ⇒ if n =? O then Some a else nth_error l' (pred n) end.
Function as data
Functions as first-class citizens
Higher-Order Functions
1 2 3 4 5
Definition doit3times {X:Type} (f:X→X) (n:X) : X := f (f (f n)).
Check @doit3times. (* ===> doit3times : forall X : Type, (X -> X) -> X -> X *)
Filter (taking a predicate on X)
collection-oriented programming style - my first time seeing this, any comments?
1 2 3 4 5 6 7 8 9 10
Fixpoint filter {X:Type} (test: X→bool) (l:list X) : (list X) := match l with | [] ⇒ [] | h :: t ⇒ if test h then h :: (filter test t) else filter test t end.
Example test_filter1: filter evenb [1;2;3;4] = [2;4]. Proof. reflexivity. Qed.
Anonymous Functions
It is arguably a little sad, in the example just above, to be forced to define the function length_is_1 and give it a name just to be able to pass it as an argument to filter
1 2 3
Example test_anon_fun': doit3times (fun n ⇒ n * n) 2 = 256. Proof. reflexivity. Qed.
Syntax: hybrid of OCaml fun n -> n and SML fn n => n. and support multi-arguments (curried)
1
Compute ((fun x y => x + y) 35).
Map
Should be familar.
1 2 3 4 5
Fixpoint map {X Y: Type} (f:X→Y) (l:list X) : (list Y) := match l with | [] ⇒ [] | h :: t ⇒ (f h) :: (map f t) end.
1 2 3
Check @map
@map : forall X Y : Type, (X -> Y) -> list X -> list Y
Slide Q&A 3
as above
option map
1 2 3 4 5
Definition option_map {X Y : Type} (f : X → Y) (xo : option X) : option Y := match xo with | None ⇒ None | Some x ⇒ Some (f x) end.
Functor Map (fmap) !
Fold (Reduce)
1 2 3 4 5
Fixpointfold {X Y: Type} (f: X→Y→Y) (l: list X) (b: Y) : Y := match l with | nil ⇒ b | h :: t ⇒ f h (fold f t b) end.
Fold Right (foldr). Argument order same with OCaml, different with Haskell.
1 2 3 4 5
Check @fold
@fold : forall X Y : Type, (X -> Y -> Y) -> list X -> Y -> Y
I encounter this problem when doing church numeral exercise.
1
Definition plus (n m : cnat) : cnat := n cnat succ m.
will result in universe inconsistency error.
1 2 3 4 5 6 7
SetPrintingUniverses. (* giving more error msg *)
In environment n : cnat m : cnat The term "cnat" has type "Type@{Top.168+1}" while it is expected to have type "Type@{Top.168}" (universe inconsistency: Cannot enforce Top.168 < Top.168 because Top.168 = Top.168).
What’s happening?
Yes, you can define: Definition plus (n m : cnat) : cnat := n cnat succ m. in System F. However, in Coq’s richer logic, you need to be a little more careful about allowing types to be instantiated at their own types, else you run into issue of inconsistency. Essentially, there is a stratification of types (by “universes”) that says that one universe cannot contain a “bigger” universe. Often, things are polymorphic in their universe (i.e., work in all universes), you run into this where you cannot instantiate the “forall X, …” that is the definition of cnat by cnat itself. – Prof. Fluet
Thus, the correct answer for that question is that Type_i has type Type_j, for any index j > i. This is needed to ensure the consistency of Coq’s theory: if there were only one Type, it would be possible to show a contradiction, similarly to how one gets a contradiction in set theory if you assume that there is a set of all sets. Coq generates one new index variable every time you write Type, and keeps track of internal constraints
The error message you saw means that Coq’s constraint solver for universe levels says that there can’t be a solution to the constraint system you asked for.
The problem is that the forall in the definition of nat is quantified over Type_i, but Coq’s logic forces nat to be itself of type Type_j, with j > i. On the other hand, the application n nat requires that j <= i, resulting in a non-satisfiable set of index constraints.
The command has indeed failed with message: The term "@identity" has type "forall A : Type, A -> A" while it is expected to have type "?A" (unable to find a well-typed instantiation for"?A": cannot ensure that "Type@{Top.1+1}" is a subtype of "Type@{Top.1}").
The link also introduce some advanced/experimental way to do polymorphic universe
Polymorphic Church Numerals w/o self-applying itself
can be used to finish a proof (shorter than rewrite then reflexivity)
It also works with conditional hypotheses:
1 2 3 4 5 6 7 8
n, m, o, p : nat eq1 : n = m eq2 : forall q r : nat, q = r -> [q; o] = [r; p] ============================ [n; o] = [m; p]
apply eq2. n = m
It works by working backwards. It will try to pattern match the universally quantified q r. (i.e. universal var) We match the conclusion and generates the hypothesis as a subgoal.
1 2 3 4 5 6 7
Theorem trans_eq : forall (X:Type) (n m o : X), n = m -> m = o -> n = o.
Example trans_eq_example' : forall (a b c d e f : nat), [a;b] = [c;d] -> [c;d] = [e;f] -> [a;b] = [e;f]. Proof. intros a b c d e f eq1 eq2. apply trans_eq. (* Error: Unable to find an instance for the variable m. *)
The unification algo won’t happy since:
it can find instance for n = o from [a;b] = [e;f] (matching both conclusion)
but what should be m? It could be anything as long as n = m and m = o holds.
So we need to tell Coq explicitly which value should be picked for m:
1
apply trans_eq with (m:=[c;d]). (* <- supplying extra info, [m:=] can be ommited *)
Prof Mtf: As a PL person, you should feel this is a little bit awkward since now function argument name must be remembered. (but it’s just local and should be able to do any alpha-conversion). named argument is more like a record.
In Coq Intensive 2 (2018), someone proposed the below which works:
1 2 3 4 5 6 7
Example trans_eq_example'' : forall (a b c d e f : nat), [a;b] = [c;d] -> [c;d] = [e;f] -> [a;b] = [e;f]. Proof. intros a b c d e f. apply trans_eq. (* Coq was able to match three at all at this time...hmm *) Qed.
injection and discrinimate
Side Note on Terminologys of Function
relation
function is defined as a special kind of binary relation. it requires xRy1 ∧ xRy2 → y1 = y2 called “functional” or “univalent”, “right-unique”, or “deterministic” and also ∀x ∈ X, ∃y ∈ Y s.t. xRy called “left-total”
x ↦ f(x)
input ↦ output
argument ↦ value
X ↦ Y
domain 域 ↦ co-domain 陪域
what can go into ↦ what possibly come out
A ⊆ X ↦ f(A) = {f(x) | x ∈ A}
↦ image
↦ what actually come out
f⁻¹(B)={x ∈ X|f(x) ∈ B} ↦ B ⊆ Y
preimage ↦
when A = X ↦ Y
↦ range
image of domain
Besides subset, the notation of image and pre-image can be applied to element as well. However, by definition:
the image of an element x of domain ↦ always single element of codomain (singleton set)
the preimage of an element y of codomain ↦ may be empty, or one, or many!
<= 1 ↦ 1 : injective (left-unique)
>= 1 ↦ 1 : surjective (right-total)
1 ↦ 1 : bijective
Noted that the definition of “function” doesn’t require “right-total”ity) until we have surjective.
graph = [(x, f(x))], these points form a “curve”, 真的是图像
Total vs Partial
For math, we seldon use partial function since we can simply “define a perfect domain for that”. But in Type Theory, Category Theory, we usually consider the domainX and the domain of definitionX'.
Besides, f(x) can be undefined. (not “left-total”, might not have “right”)
Bijective: 1 ↦ 1 (intersection of Inj and Surj, so only 1 preimage, one-to-one correspondence)
injectivitiy and disjointness, or inversion.
Recall the definition of nat:
1 2 3
Inductive nat : Type := | O : nat | S : nat → nat.
Besides there are two forms of nat (for destruct and induction), there are more facts:
The constructor S is injective (distinct), i.e S n = S m -> n = m.
The constructors O and S are disjoint, i.e. forall n, O != S n .
injection
can be used to prove the preimages are the same.
injection leave things in conclusion rather than hypo. with as would be in hypo.
disjoint
principle of explosion (a logical principle)
asserts a contraditory hypothesis entails anything. (even false things)
vacously true
false = true is contraditory because they are distinct constructors.
inversion
the big hammer: inversion of the definition.
combining injection and disjoint and even some more rewrite.
IMH, which one to use depends on semantics
from Coq Intensive (not sure why it’s not the case in book version).
1 2 3 4 5 6 7 8 9 10
Theorem S_injective_inv : forall (n m : nat), S n = S m -> n = m. Proof. intros n m H. inversion H. reflexivity. Qed.
Theorem inversion_ex1 : forall (n m : nat), [n] = [m] -> n = m. Proof. intros n m H. inversion H. reflexivity. Qed.
Side question: could Coq derive equality function for inductive type? A: nope. Equality for some inductive types are undecidable.
Converse of injectivity
1 2 3 4 5
Theoremf_equal : ∀(A B : Type) (f: A → B) (x y: A), x = y → f x = f y. Proof. intros A B f x y eq. rewrite eq. reflexivity. Qed.
Slide Q&A 1
The tactic fails because tho negb is injective but injection only workks on constructors.
Using Tactics in Hypotheses
Reasoning Backwards and Reasoning Forward (from Coq Intensive 2)
Style of reasoning
Backwards: start with goal, applying tactics simpl/destruct/induction, generate subgoals, until proved.
iteratively reasons about what would imply the goal, until premises or previously proven theorems are reached.
Forwards: start with hypo, applying tactics, iteratively draws conclusions, until the goal is reached.
Backwards reasoning is dominated stratgy of theroem prover (and execution of prolog). But not natural in informal proof.
True forward reasoning derives fact, but in Coq it’s like hypo deriving hypo, very imperative.
in
most tactics also have a variant that performs a similar operation on a statement in the context.
1 2 3 4 5
simplin H. simplin *. (* in all hypo and goal *)
symmetryin H. apply L in H.
applying in hypothesis and in conclusion
applying in hypo is very different with applying in conclusion.
it’s not we unify the ultimate conclusion and generate premises as new goal, but trying to find a hypothesis to match and left the residual conclusion as new hypothesis.
1 2 3 4 5 6 7 8
Theorem silly3'' : forall (n : nat), (true = (n =? 5) -> true = ((S (S n)) =? 7)) -> true = (n =? 5) -> true = ((S (S n)) =? 7). Proof. intros n eq H. apply eq in H. (* or *)apply eq. (* would be different *) apply H. Qed.
Also if we add one more premises true = true ->, the subgoal generated by apply would be in reversed order:
Again: “proof engineering”: proof can be done in so many different ways and in different orders.
Varying the Induction Hypothesis
Sometimes it’s important to control the exact form of the induction hypothesis!!
Considering:
1 2
Theorem double_injective: ∀n m, double n = double m → n = m.
if we begin with intros n m. induction n. then we get stuck in the inductive case of n, where the induction hypothesis IHn' generated is:
1 2
IHn' : double n' = double m -> n' = m IHn' : double n' = double (S m') -> n' = S m' (* m = S m' *)
This is not what we want!!
To prove double_injective, we hope IHn' can give us double n' = double m' -> n' = m' (i.e. the P(n-1) case).
The problem is intros implies for these particular n and m. (not more forall but const). And when we intros n m. induction n, we are trying to prove a statement involving every n but just a single m…
How to keep m generic (universal)?
By either induction n before intros m or using generalize dependent m, we can have:
1
IHn' : forall m : nat, double n' = double m -> n' = m
where the m here is still universally quantified, so we can instaniate m with m' by applying it with double n' = double m' to yield n' = m' or vice versa. (recall conditional statements can be applyed in 2 ways.)
Notes on generalize dependent
Usually used when the argument order is conflict with instantiate (intros) order.
? reflection: turing a computational result into a propositional result
Unfolding Definitions.
tactics like simpl, reflexivity, and apply will often unfold the definitions of functions automatically. However, this automatic unfolding is somewhat conservative.
simpl. only do unfolding when it can furthur simplify after unfolding. But sometimes you might want to explicitly unfold then do furthur works on that.
Using destruct on Compound Expressions
destruct the whole arbitrary expression.
destruct by default throw away the whole expression after it, which might leave you into a stuck state. So explicitly saying eqn:Name would help with that!
Micro Sermon - Mindless proof-hacking
From Coq Intensive…
a lot of fun
…w/o thinking at all
terrible temptation
you shouldn’t always resist…
But after 5 mins…you should step back and try to think
A typical coq user
sitting and does not have their brain engaged all the time…
proofs: ways of presenting evidence for the truth of a proposition
Prop type
1 2
Check3 = 3.(* ===> Prop. A provable prop *) Check3 = 4.(* ===> Prop. A unprovable prop *)
Prop is first-class entity we can
name it
parametrized!
1 2 3 4
Definition is_three (n : nat) : Prop := n = 3.
Check is_three. (* ===> nat -> Prop *)
Properties
In Coq, functions that return propositions are said to define properties of their arguments.
1 2 3 4 5
Definition injective {A B} (f : A → B) := ∀x y : A, f x = f y → x = y. Lemma succ_inj : injective S. (* can be read off as "injectivity is a property of S" *) Proof. intros n m H. injection H as H1. apply H1. Qed.
The equality operator = is also a function that returns a Prop. (property: equality)
1
Check @eq. (* ===> forall A : Type, A -> A -> Prop *)
Theroems are types, and proofs are existentials.
Slide Q&A - 1.
Prop
Prop
Prop
Not typeable
nat -> nat
nat -> Prop
(3)
think that Big Lambda (the type abstraction) works at type level, accepts type var, substitute and reture a type. forall in Coq is same (the concrete syntax) and only typecheck with Type or its subtype Set & Prop.
1 2 3 4
Check (∀n:nat, S (pred n)). (* not typeable *)
Definition foo : (forall n:nat, bool) (* foo: nat -> bool *) := fun x => true.
Logical Connectives
noticed that connectives symbols are “unicodize” in book and spacemacs.
Conjuction (logical and)
and is just binary Prop -> Prop -> Prop and associative.
1 2 3
Print"/\". Inductive and (A B : Prop) : Prop := conj : A -> B -> A /\ B Check and. (* ===> and : Prop -> Prop -> Prop *)
and introduction
1 2 3 4 5 6
Lemma and_intro : forall A B : Prop, A -> B -> A /\ B. Proof. intros A B HA HB. split. - apply HA. - apply HB. Qed.
To prove a conjunction,
use the split tactic. It will generate two subgoals,
or use apply and_intro., which match the conclusion and give its two premises as your subgoals.
and elimination
if we already have a proof of and, destruct can give us both.
1 2 3 4 5
Lemma and_example2' : ∀n m : nat, n = 0 ∧ m = 0 → n + m = 0. Proof. intros n m [Hn Hm]. (* = intro H. destruct H. *) rewrite Hn. rewrite Hm. reflexivity. Qed. (* you could use only one *)
Instead of packing into conjunction ∀n m : nat, n = 0 ∧ m = 0 → n + m = 0. why not two separate premises? ∀n m : nat, n = 0 -> m = 0 → n + m = 0. Both are fine in this case but conjunction are useful as intermediate step etc.
Coq Intensive Q: why destruct can work on and? is and inductively defined? A: Yes.
Disjunction (locial or)
or elimination
We need do case analysis (either P or Q should be able to prove the theroem separately!)
1 2 3 4 5 6 7 8
Lemma or_example : forall n m : nat, n = 0 \/ m = 0 -> n * m = 0. Proof. (* This pattern implicitly does case analysis on [n = 0 \/ m = 0] *) intros n m [Hn | Hm]. (* = intro H. destruct H. *) - (* Here, [n = 0] *)rewrite Hn. reflexivity. - (* Here, [m = 0] *)rewrite Hm. rewrite <- mult_n_O. reflexivity. Qed.
or introduction
When trying to establish (intro into conclusion) an or, using left or right to pick one side to prove is sufficient.
1 2 3 4 5 6
Lemma or_intro : forall A B : Prop, A -> A \/ B. Proof. intros A B HA. left. (* tactics *) apply HA. Qed.
Falsehood and negation
False?
Recall the princple of explosion: it asserts that, if we assume a contradiction, then any other proposition can be derived. we could define ¬ P (“not P”) as ∀ Q, P → Q..
Coq actually makes a slightly different (but equivalent) choice, defining ¬ P as P → False, where False is a specific contradictory proposition defined in the standard library.
1 2
Definition not (P:Prop) := P → False. Notation"¬x" := (not x) : type_scope.
Prove the princple of explosion:
1 2 3 4 5
Theorem ex_falso_quodlibet : forall (P:Prop), False -> P. Proof. intros P contra. destruct contra. Qed. (* 0 cases to prove since ⊥ is not provable. [inversion] also works *)
Inequality
1
Notation"x <> y" := (~(x = y)).
Same as SML and OCaml (for structural equality, OCaml uses != for physical equality.)
Proving of negation (or how to prove ¬P)
thinking about as unfold not, i.e. P -> False. so you have an assumptions P that could be intros HP. and the residual goal would be simply False. which is usually proved by some kind of contradiction in hypotheses with tactics discriminate. or contradiction.
1 2 3 4 5 6
Theorem contradiction_implies_anything : forall P Q : Prop, (P /\ ~P) -> Q. Proof. intros P Q [HP HNA]. (* we could [contradiction.] to end the proof here`*) unfold not in HNA. apply HNA in HP. (* HP : False, HNA : P -> False ⊢ HP: False *) destruct HP. Qed. (* destruct False. *)
Tactic exfalso.
If you are trying to prove a goal that is nonsensical (e.g., the goal state is false = true), apply ex_falso_quodlibet to change the goal to False. This makes it easier to use assumptions of the form ¬P that may be available in the context — in particular, assumptions of the form x≠y.
Since reasoning with ex_falso_quodlibet is quite common, Coq provides a built-in tactic, exfalso, for applying it.
Slide Q&A - 2
?unfold is implicit
only destruct (if we consider intros destruct is also destruct.), ?unfold
none (?unfold)
left.
destruct, unfold, left and right
discrinminate (or inversion)
Truth
1 2
Lemma True_is_true : True. Proof. apply I. Qed.
I : True is a predefined Prop…
Logical Equivalence
if and only if is just the conjunction of two implications. (so we need split to get 2 subgoals)
rewrite and reflexivity can be used with iff statements, not just equalities.
Existential Quantification
To prove a statement of the form ∃x, P, we must show that P holds for some specific choice of value for x, known as the witness of the existential.
So we explicitly tell Coq which witness t we have in mind by invoking exists t. then all occurences of that “type variable” would be replaced.
Intro
1 2 3 4
Lemma four_is_even : exists n : nat, 4 = n + n. Proof. exists2.reflexivity. Qed.
Elim
Below is an interesting question…by intros and destruct we can have equation n = 4 + m in hypotheses.
1 2 3 4 5 6 7
Theorem exists_example_2 : forall n, (exists m, n = 4 + m) -> (exists o, n = 2 + o). Proof. intros n [m Hm]. (* note implicit [destruct] here *) exists (2 + m). apply Hm. Qed.
Programming with Propositions
Considering writing a common recursive is_in for polymorphic lists. (Though we dont have a polymorphic =? (eqb) defined yet)
1 2 3 4 5
Fixpoint is_in {A : Type} (x : A) (l : list A) : bool := match l with | [] => false | x' :: l' => if (x' =? x) then true else is_in x l' end.
Similarly, we can write this function but with disjunction and return a Prop! so we can write function to generate/create statements/propositions! (thx for the idea Prop is first-class)
1 2 3 4 5
Fixpoint In {A : Type} (x : A) (l : list A) : Prop := match l with | [] => False | x' :: l' => x' = x ∨ In x l' end.
The whole thing I understood as a type operator (function in type level) and it’s recursive!
Coq dare to do that becuz its total, which is guarded by its termination checker. un-total PL, if having this, would make its type system Turing Complete (thus having Halting Problem). (Recursive Type like ADT/GADT in ML/Haskell is a limited form of recursion allowing no arbitray recursion.)
In_map
I took this one since it’s like a formal version of Property-based Tests!.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
Lemma In_map : forall (A B : Type) (f : A -> B) (l : list A) (x : A), In x l -> In (f x) (map f l). Proof. intros A B f l x. induction l as [|x' l' IHl']. - (* l = nil, contradiction *) simpl. intros []. - (* l = x' :: l' *) simpl. intros [H | H]. (* evaluating [In] gives us 2 cases: *) + rewrite H. left. reflexivity. (* in head of l *) + right. apply IHl'. apply H. (* in tail of l*) Qed.
Q & A:
eq is just another inductively defined and doesn’t have any computational content. (satisfication)
Why use Prop instead of bool? See reflection below.
Drawbacks
In particular, it is subject to Coq’s usual restrictions regarding the definition of recursive functions, e.g., the requirement that they be “obviously terminating.”
In the next chapter, we will see how to define propositions inductively, a different technique with its own set of strengths and limitations.
Applying Theorems to Arguments.
Check some_theorem print the statement!
1 2
Check plus_comm. (* ===> forall n m : nat, n + m = m + n *)
Coq prints the statement of the plus_comm theorem in the same way that it prints the type of any term that we ask it to Check. Why?
Hmm…I just noticed that!! But I should notice because Propositions are Types! (and terms are proof)
Proof Object
proofs as first-class objects.
After Qed., Coq defined they as Proof Object and the type of this obj is the statement of the theorem.
Provable: some type is inhabited by some thing (having terms).
So I guess when we apply theorems, Coq implicitly use the type of the Proof Object. (it’s already type abstraction) …we will get to there later at ProofObject chapter.
Apply theorem as function
rewrite select variables greedily by default
1 2 3 4 5 6 7 8
Lemma plus_comm3_take3 : ∀x y z, x + (y + z) = (z + y) + x. Proof. intros x y z. rewrite plus_comm. rewrite (plus_comm y z). (* we can explicitly provide type var! *) reflexivity. Qed.
x y z were some type var and instantiated to values by intros, e.g. x, y, z:nat but we can explicilty pass in to plus_comm, which is a forall type abstraction! (Δ n m. (eq (n + m) (m + n)))
there must be something there in Proof Object so we can apply values to a type-level function
Coq vs. Set Theory
Coq’s logical core, the Calculus of Inductive Constructions, is a metalanguage for math, but differs from other foundations of math e.g. ZFC Set Theory
Functional Extensionality
1 2 3 4 5
(∀x, f x = g x) → f = g
∃f g, (∀x, f x = g x) → f = g
∃f g, (∀x, f x = g x) ∧ f != g (* negation, consistent but not interesting... *)
In common math practice, two functions f and g are considered equal if they produce the same outputs. This is known as the principle of functional extensionality.
This is not built-in Coq, but we can add them as Axioms. Why not add everything?
One or multiple axioms combined might render inconsistency.
Code extraction might be problematic
Fortunately, it is known that adding functional extensionality, in particular, is consistent. consistency: a consistent theory is one that does not contain a contradiction.
Adding Axioms
1 2 3
Axiom functional_extensionality : forall {X Y: Type} {f g : X -> Y}, (forall (x:X), f x = g x) -> f = g.
It’s like Admitted. but alerts we’re not going to fill in later.
Exercise - Proving Reverse with app and with cons are fn-exensionally equivalent.
1 2 3 4 5 6 7 8
Fixpoint rev_append {X} (l1 l2 : list X) : list X := match l1 with | [] => l2 | x :: l1' => rev_append l1' (x :: l2) end.
Definition tr_rev {X} (l : list X) : list X := rev_append l [].
BTW, this version is tail recursive becuz the recursive call is the last operation needs to performed. (In rev i.e. rev t ++ [h], recursive call is a argument of function ++ and we are CBV.)
1
Lemma tr_rev_correct : forall X, @tr_rev X = @rev X.
Propositions and Booleans
We’ve seen two different ways of expressing logical claims in Coq:
with booleans (of type bool), ; computational way
with propositions (of type Prop). ; logical way
There’re two ways to define 42 is even:
1 2
Example even_42_bool : evenb 42 = true. Example even_42_prop : ∃k, 42 = double k.
We wanna show there are interchangable.
1 2
Theorem even_bool_prop : ∀n, evenb n = true ↔ ∃k, n = double k.
In view of this theorem, we say that the boolean computation evenb nreflects the truth of the proposition ∃ k, n = double k.
We can futhur general this to any equations representing as bool or Prop:
However, even they are equivalent from a purely logical perspective, they may not be equivalent operationally.
1 2 3 4 5
Fail Definition is_even_prime n := if n = 2then true else false.
Error: The term "n = 2" has type "Prop" which is not a (co-)inductive type.
=, or eq, as any function in Coq, need to be computable and total. And we have no way to tell whether any given proposition is true or false. (…We can only naturally deduce things are inductively defined)
As a consequence, Prop in Coq does not have a universal case analysis operation telling whether any given proposition is true or false, since such an operation would allow us to write non-computable functions.
Although general non-computable properties cannot be phrased as boolean computations, it is worth noting that even many computable properties are easier to express using Prop than bool, since recursive function definitions are subject to significant restrictions in Coq.
E.g. Verifying Regular Expr in next chapter.
Doing the same with bool would amount to writing a full regular expression matcher (so we can execute the regex).
Proof by Reflection!
1 2 3 4 5 6 7 8 9 10 11
(* Logically *) Example even_1000 : ∃k, 1000 = double k. Proof. ∃500.reflexivity. Qed.
(* Prove logical version by reflecting in computational version *) Example even_1000'' : ∃k, 1000 = double k. Proof. apply even_bool_prop. reflexivity. Qed.
As an extreme example, the Coq proof of the famous 4-color theorem uses reflection to reduce the analysis of hundreds of different cases to a boolean computation.
Classical vs. Constructive Logic
…
Future Schedule
Proof got messier! Lean on your past PLT experience
Theorem even_bool_prop : ∀n, evenb n = true ↔ ∃k, n = double k. (*bool*)(*prop*)
we can write an Inductive definition of the even property!
Inference rules
In CS, we often uses inference rules
ev n
---- ev_0 ------------ ev_SS
ev 0 ev (S (S n))
and proof tree (i.e. evidence), there could be multiple premieses to make it more tree-ish.
---- ev_0
ev 0
---- ev_SS
ev 2
---- ev_SS
ev 4
So we can literally translate them into a GADT:
Inductive Definition of Evenness
1 2 3 4 5 6
Inductive even : nat → Prop := | ev_0 : even 0 | ev_SS : ∀n, even n → even (S (S n)).
Check even_SS. (* ==> : forall n : nat, even n -> even (S (S n)) *)
There are two ways to understand the even here:
1. A Property of nat and two theorems (Intuitively)
the thing we are defining is not a Type, but rather a function nat -> Prop — i.e., a property of numbers.
we have two ways to provide an evidence to show the nat is even, either or:
it’s 0, we can immediately conclude it’s even.
for any n, if we can provide a evidence that n is even, then S (S n) is even as well.
We can think of the definition of even as defining a Coq property even : nat → Prop, together with primitive theorems ev_0 : even 0 and ev_SS : ∀ n, even n → even (S (S n)).
2. An “Indexed” GADT and two constructors (Technically)
In an Inductive definition, an argument to the type constructor on the left of the colon is called a “parameter”, whereas an argument on the right is called an “index”. – “Software Foundaton”
Considered a “parametrized” ADT such as the polymorphic list,
1 2 3 4 5
Inductive list (X:Type) : Type := | nil | cons (x : X) (l : list X).
Check list. (* ===> list : Type -> Type *)
where we defined type con list : Type -> Type, by having a type var X in the left of the :. the X is called a parameter and would be parametrized i.e. substituted, globally, in constructors.
Here, we write nat in the right of the : w/o giving it a name (to refer and to substitute), which allows the nat taking different values in different constructors (as constraints). it’s called an index and will form a family of type indexed by nat (to type check?)
From this perspective, there is an alternative way to write this GADT:
1 2 3
Inductive even : nat → Prop := | ev_0 : even 0 | ev_SS (n : nat) (H : even n) : even (S (S n)).
we have two ways to construct the even type (Prop <: Type), either or:
ev_0 takes no argument, so simply instantiate even with nat 0
ev_SS takes a natn and a H typed even n,
the dependency between two arguments thus established!
as long as the constraint on same n is fullfilled, we can build type even with S (S n)
The take way is that dependent type (Pi-type) allow us to constriant constructors with different values.
indexed way is more general. it formed a larger type, and is only used when extra power needed. every parametrized one can be represented as indexed one (it’s just that index happended to be the same)
“Constructor Theorems”
Such “constructor theorems” have the same status as proven theorems. In particular, we can use Coq’s apply tactic with the rule names to prove even for particular numbers…
Besides constructing evidence that numbers are even, we can also reason about such evidence.
Introducing even with an Inductive declaration tells Coq that these two constructors are the only ways to build evidence that numbers are even.
In other words, if someone gives us evidence E for the assertion even n, then we know that E must have one of two shapes
This suggests that it should be possible to analyze a hypothesis of the form even n much as we do inductively defined data structures; in particular, it should be possible to argue by induction and case analysis on such evidence.
This starts to get familiar as what we did for many calculi, ranging from Logics to PLT. This is called the Inversion property.
Inversion on Evidence
We can prove the inersion property by ourselves:
1 2 3 4 5 6 7 8 9
Theorem ev_inversion : ∀(n : nat), even n → (n = 0) ∨ (∃n', n = S (S n') ∧ even n'). Proof. intros n E. destruct E as [ | n' E']. - (* E = ev_0 : even 0 *)left. reflexivity. - (* E = ev_SS n', E' : even (S (S n')) *)right. ∃n'. split. reflexivity. apply E'. Qed.
But Coq provide the inversion tactics that does more! (not always good tho, too automagical)
The inversion tactic does quite a bit of work. When applied to equalities, as a special case, it does the work of both discriminate and injection. In addition, it carries out the intros and rewrites
Here’s how inversion works in general. Suppose the name H refers to an assumption P in the current context, where P has been defined by an Inductive declaration. Then, for each of the constructors of P, inversion H generates a subgoal in which H has been replaced by the exact, specific conditions under which this constructor could have been used to prove P. Some of these subgoals will be self-contradictory; inversion throws these away. The ones that are left represent the cases that must be proved to establish the original goal. For those, inversion adds all equations into the proof context that must hold of the arguments given to P (e.g., S (S n') = n in the proof of evSS_ev). (9-proof-object.md has a better explaination on inversion)
inversion is a specific use upon destruct (both do case analysis on constructors), but many property need induction!. By induction (even n), we have cases and subgoals splitted, and induction hypothesis as well.
Induction on Evidence
Similar to induction on inductively defined data such as list:
To prove a property of (for any X) list X holds, we can use induction on list X. To prove a property of n holds for all numbers for which even n holds, we can use induction on even n.
Notes on induction
The principle of induction is to prove P(n-1) -> P(n) (多米诺) for some (well-founded partial order) set of n.
Here, we are induction over “the set of numbers fullfilling the property even“. Noticed that we r proving things over this set, meaning we already have it (i.e. a proof, or a evidence) in premises, instead of proving the evenness of the set.
Proof by Mathematical Induction is Deductive Reasoning
“Proof by induction,” despite the name, is deductive. The reason is that proof by induction does not simply involve “going from many specific cases to the general case.” Instead, in order for proof by induction to work, we need a deductive proof that each specific case implies the next specific case. Mathematical induction is not philosophical induction. https://math.stackexchange.com/a/1960895/528269
Mathematical induction is an inference rule used in formal proofs. Proofs by mathematical induction are, in fact, examples of deductive reasoning. Equivalence with the well-ordering principle: The principle of mathematical induction is usually stated as an axiom of the natural numbers; see Peano axioms. However, it can be proved from the well-ordering principle. Indeed, suppose the following: https://en.wikipedia.org/wiki/Mathematical_induction
Also, Structual Induction is one kind of Math. Induction
和标准的数学归纳法等价于良序原理一样,结构归纳法也等价于良序原理。
…A well-foundedpartial order is defined on the structures… …Formally speaking, this then satisfies the premises of an axiom of well-founded induction… https://en.wikipedia.org/wiki/Structural_induction
In terms of Well-ordering and Well-founded:
If the set of all structures of a certain kind admits a well-founded partial order, then every nonempty subset must have a minimal element. (This is the definition of “well-founded”.) 如果某种整个结构的集容纳一个良基偏序, 那么每个非空子集一定都含有最小元素。(其实这也是良基的定义
Inductive Relations
Just as a single-argument proposition defines a property, 性质 a two-argument proposition defines a relation. 关系
1 2 3 4 5
Inductive le : nat → nat → Prop := | le_n n : le n n | le_S n m (H : le n m) : le n (S m).
Notation"n ≤ m" := (le n m).
It says that there are two ways to give evidence that one number is less than or equal to another:
either same number
or give evidence that n ≤ m then we can have n ≤ m + 1.
and we can use the same tactics as we did for properties.
Slide Q&A - 1
First destructeven n into 2 cases, then discriminate on each.
Another way… rewriting n=1 on even n. It won’t compute Prop, but destruct can do some discriminate behind the scene.
Slide Q&A - 2
inversion and rewrite plus_comm (for n+2)
destruct vs. inversion vs. induction.
destruct, inversion, induction (on general thing)… similar/specialized version of each…
Trying to internalize this concept better: When to use which?
For any inductively defined proposition (<: Type) in hypothesis: meaning from type perspective, it’s already a “proper type” (::*)
1
Inductive P = C1 : P1 | C2 : A2 -> P2 | ...
destruct case analysis on inductive type
simply give you each cases, i.e. each constructors.
we can destruct on a =? b since =? is inductively defined.
induction use induction principle
proving P holds for all base cases
proving P(n) holds w/ P(n-1) for all inductive cases (destruct stucks in this case because of no induction hypothesis gained from induction principle)
inversion invert the conclusion and give you all cases with premises of that case.
For GADT, i.e. “indexed” Prop (property/relation), P could have many shape inversion give you Ax for shape P assuming built with Cx
inversion discards cases when shape P != Px. (destruct stucks in this case because of no equation gained from inversion lemma)
Case Study: Regular Expressions
Definition
Definition of RegExp in formal language can be found in FCT/CC materials
Thinking about their NFA: they both have non-deterministic branches! The recursive occurrences of exp_match gives as direct argument (evidence) about which branches we goes.
we need some sanity check since Coq simply trust what we declared… that’s why there is even Quick Check for Coq.
Direct Proof
In fact, MApp is also non-deterministic about how does re1 and re2 collaborate… So we have to be explicit:
This, if we want to prove via destruct, we have to write our own inversion lemma (like ev_inversion for even). Otherwise we have no equation (which we should have) to say contradiction.
1 2 3 4
Example reg_exp_ex3 : ~ ([1; 2] =~ Char 1). Proof. intros H. inversion H. Qed.
Manual Manipulation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Lemma MStar1 : forall T s (re : @reg_exp T) , s =~ re -> s =~ Star re. Proof. intros T s re H. rewrite <- (app_nil_r _ s). (* extra "massaging" to convert [s] => [s ++ []] *) apply (MStarApp s [] re). (* to the shape [MStarApp] expected thus can pattern match on *)
(* proving [MStarApp] requires [s1 s2 re H1 H2]. By giving [s [] re], we left two evidence *) | MStarApp s1 s2 re (H1 : exp_match s1 re) (H2 : exp_match s2 (Star re)) : exp_match (s1 ++ s2) (Star re).
- apply H. (* evidence H1 *) - apply MStar0. (* evidence H2 *) Qed. (* the fun fact is that we can really think the _proof_ as providing evidence by _partial application_. *)
Induction on Evidence
By the recursive nature of exp_match, proofs will often require induction.
1 2 3 4 5 6 7 8 9 10
(** Recursively collecting all characters that occur in a regex **) Fixpoint re_chars {T} (re : reg_exp) : list T := match re with | EmptySet ⇒ [] | EmptyStr ⇒ [] | Char x ⇒ [x] | App re1 re2 ⇒ re_chars re1 ++ re_chars re2 | Union re1 re2 ⇒ re_chars re1 ++ re_chars re2 | Star re ⇒ re_chars re end.
The proof of in_re_match went through by inversion on relation s =~ re. (which gives us all 7 cases.) The interesting case is MStarApp, where the proof tree has two branches (of premises):
s1 =~ re s2 =~ Star re
--------------------------- (MStarApp)
s1 ++ s2 =~ Star re
So by induction on the relation (rule), we got two induction hypotheses! That’s what we need for the proof.
The remember tactic (Induction on Evidence of A Specific Case)
One interesting/confusing features is that induction over a term that’s insuffciently general. e.g.
1 2 3 4 5 6
Lemma star_app: ∀T (s1 s2 : list T) (re : @reg_exp T), s1 =~ Star re → s2 =~ Star re → s1 ++ s2 =~ Star re. Proof. intros T s1 s2 re H1.
Here, we know the fact that both s1 and s2 are matching with the form Star re. But by induction. it will give us all 7 cases to prove, but 5 of them are contradictory!
That’s where we need remember (Star re) as re' to get this bit of information back to discriminate.
Sidenotes: inversion vs. induction on evidence
We might attemp to use inversion, which is best suitted for have a specific conclusion of some rule and inverts back to get its premises.
But for recursive cases (e.g. Star), we always need induction.
induction on a specific conclusion then remember + contradiction is similar with how inversion solves contradictionary cases. (They both destruct the inductively defined things for sure)
The only way to construct ReflectT/F is by showing (a proof) of P/¬P, meaning invertion on reflect P bool can give us back the evidence.
iff_reflect give us eqbP.
1 2 3 4
Lemma eqbP : ∀n m, reflect (n = m) (n =? m). Proof. intros n m. apply iff_reflect. rewrite eqb_eq. reflexivity. Qed.
This gives us a small gain in convenience: we immediately give the Prop from bool, no need to rewrite.
Proof Engineering Hacks…
SSReflect - small-scale reflection
a Coq library used to prove 4-color theorem…! simplify small proof steps with boolean computations. (somewhat automation with decision procedures)
Extended Exercise: A Verified Regular-Expression Matcher
we have defined a match relation that can prove a regex matches a string. but it does not give us a program that can run to determine a match automatically…
we hope to translate inductive rules (for constructing evidence) to recursive fn. however, since reg_exp is recursive, Coq won’t accept it always terminates
theoritically, the regex = DFA so it is decidable and halt. technically, it only halts on finite strings but not infinite strings. (and infinite strings are probably beyond the scope of halting problem?)
Heavily-optimized regex matcher = translating into state machine e.g. NFA/DFA. Here we took a derivative approach which operates purely on string.
1 2
RequireExport Coq.Strings.Ascii. Definition string := list ascii.
Coq 标准库中的 ASCII 字符串也是归纳定义的,不过我们这里为了之前定义的 match relation 用 list ascii.
to define regex matcher over list X i.e. polymorphic lists. we need to be able to test equality for each X etc.
partial maps, return option to indicate success/failure, using None as the default.
The Coq Standard Lib
From now on, importing from std lib. (but should not notice much difference)
1 2 3 4 5 6
From Coq RequireImport Arith.Arith. From Coq RequireImport Bool.Bool. RequireExport Coq.Strings.String. From Coq RequireImport Logic.FunctionalExtensionality. From Coq RequireImport Lists.List. Import ListNotations.
TODO: what’s the differences above? Answered in Coq Intensive:
Require give access but need to use qualified name
Import no need to use qualified name
Export module importing me no need to use qualified name as well
String in Coq is list of Char and Char is record of 8 Bool…
Identifiers
we need a type for the keys that we use to index into our maps.
In Lists.v (Partial Maps):
1 2
Inductive id : Type := | Id (n : nat).
From now on we will use the string from Coq’s std lib:
1 2 3 4 5
Definition eqb_string (x y : string) : bool := if string_dec x y then true else false.
The equality check fn for string from stdlib is string_des, which returns a sumbool type, i.e. {x=y} + {x≠y}.
which can be thought of as an “evidence-carrying boolean”. Formally, an element of sumbool is either or
a proof that two things are equal
a proof that they are unequal, together with a tag indicating which.
Some properties:
1 2 3 4 5 6
(* reflexive relation *) Theorem eqb_string_refl : ∀s : string, true = eqb_string s s.
(* functional extensionality *) Theorem eqb_string_true_iff : ∀x y : string, eqb_string x y = true ↔ x = y. Theorem eqb_string_false_iff : ∀x y : string, eqb_string x y = false ↔ x ≠ y.
Total Maps
use functions, rather than lists of key-value pairs, to build maps. The advantage of this representation is that it offers a more extensional view of maps. 外延性
(where two maps that respond to queries in the same way will be represented as literally the same thing rather than just “equivalent” data structures. This, in turn, simplifies proofs that use maps.)
1 2 3 4 5 6 7 8 9 10
Definition total_map (A : Type) := string -> A.
(* empty take a default value *) Definition t_empty {A : Type} (v : A) : total_map A := (fun_ => v).
(* update take a key value pair *) Definition t_update {A : Type} (m : total_map A) (x : string) (v : A) (* : total_map A *) := fun x' => if eqb_string x x' then v else m x'.
Noticed that the “Map building” is in a reversed order…
Note that we don’t need to define a find operation because it is just function application!
1 2 3
Example update_example2 : examplemap' "foo" = true. Example update_example4 : examplemap' "bar" = true. Example update_example1 : examplemap' "baz" = false. (* default *)
Partial Maps
we define partial maps on top of total maps. A partial map with elements of type A is simply a total map with elements of type option A and default element None.
Definition update {A : Type} (m : partial_map A) (x : string) (v : A) := (x !-> Some v ; m). Notation"x '⊢>' v ';' m" := (update m x v) (at level 100, v at next level, right associativity).
(** hide the empty case. Since it's always [None] **) Notation"x '⊢>' v" := (update empty x v) (at level 100). (** so nice **) Example examplepmap := ("Church" ⊢> true ; "Turing" ⊢> false).
we use the “standard” map operator ↦ for partial map since maps in CS are usually partial.
“Algorithms are the computational content of proofs.” —Robert Harper
So the book material is designed to be gradually reveal the facts that
Programming and proving in Coq are two sides of the same coin.
e.g.
Inductive is useds for both data types and propositions.
-> is used for both type of functions and logical implication.
The fundamental idea of Coq is that:
provability in Coq is represented by concrete evidence. When we construct the proof of a basic proposition, we are actually building a tree of evidence, which can be thought of as a data structure.
e.g.
implication like A → B, its proof will be an evidence transformer: a recipe for converting evidence for A into evidence for B.
Proving manipulates evidence, much as programs manipuate data.
Curry-Howard Correspondence
deep connection between the world of logic and the world of computation:
Check (ev_SS 2 (ev_SS 0 ev_0)). (* concrete derivation tree, we r explicitly say the number tho *) (* ===> even 4 *)
These two ways are the same in principle!
Proof Scripts
Show Proof. will show the partially constructed proof terms / objects. ?Goal is the unification variable. (the hold we need to fill in to complete the proof)
more complicated in branching cases one hole more subgoal
Tactic proofs are useful and convenient, but they are not essential: in principle, we can always construct the required evidence by hand
Agda doesn’t have tactics built-in. (but also Interactive)
Quantifiers, Implications, Functions
In Coq’s computational universe (where data structures and programs live), to give ->:
constructors (introduced by Indutive)
functions
in Coq’s logical universe (where we carry out proofs), to give implication:
constructors
functions!
So instead of writing proof scripts e.g._
1 2 3 4 5 6 7
Theorem ev_plus4 : ∀n, even n → even (4 + n). Proof. intros n H. simpl. apply ev_SS. apply ev_SS. apply H. Qed.
we can give proof object, which is a function here, directly!
1 2 3 4 5 6 7 8 9
Definition ev_plus4' : ∀n, even n → even (4 + n) := (* ∀ is syntax for Pi? *) fun (n : nat) ⇒ fun (H : even n) ⇒ ev_SS (S (S n)) (ev_SS n H).
Definition ev_plus4'' (n : nat) (H : even n) (* tricky: implicitly `Pi` when `n` get mentioned? *) : even (4 + n) := ev_SS (S (S n)) (ev_SS n H).
two interesting facts:
intros x corresponds to λx. (or Pi x.??)
apply corresponds to…not quite function application… but more like filling the hole.
even n mentions the value of 1st argument n. i.e. dependent type!
Recall Ari’s question in “applying theorem as function” e.g. plus_comm why we can apply value in type-level fun. becuz of dependent type.
Now we call them dependent type function
→ is degenerated ∀ (Pi)
Notice that both implication (→) and quantification (∀) correspond to functions on evidence. In fact, they are really the same thing: → is just a shorthand for a degenerate use of ∀ where there is no dependency, i.e., no need to give a name to the type on the left-hand side of the arrow:
1 2 3 4 5 6 7
∀(x:nat), nat = ∀(_:nat), nat = nat → nat
∀n, ∀(E : even n), even (n + 2). = ∀n, ∀(_ : even n), even (n + 2). = ∀n, even n → even (n + 2).
In general, P → Q is just syntactic sugar for ∀ (_:P), Q.
TaPL also mention this fact for Pi.
Q&A - Slide 15
∀ n, even n → even (4 + n). (2 + n = S (S n))
Programming with Tactics.
If we can build proofs by giving explicit terms rather than executing tactic scripts, you may be wondering whether we can build programs using tactics? Yes!
Definition add1 : nat → nat. intro n. ShowProof. (** the goal (proof state): n : nat ======= nat the response: (fun n : nat => ?Goal) What is really interesting here, is that the premies [n:nat] is actually the arguments! again, the process of applying tactics is _partial application_ **)
apply S. ShowProof. (** (fun n : nat => S ?Goal) **) apply n. Defined.
Print add1. (* ==> add1 = fun n : nat => S n : nat -> nat *)
Notice that we terminate the Definition with a . rather than with := followed by a term. This tells Coq to enter proof scripting mode (w/o Proof., which did nothing)
Also, we terminate the proof with Defined rather than Qed; this makes the definition transparent so that it can be used in computation like a normally-defined function (Qed-defined objects are opaque during computation.).
Qed make things unfoldable, thus add 1 ends with Qed is not computable… (becuz of not even unfoldable thus computation engine won’t deal with it)
Prof.Mtf: meaning “we don’t care about the details of Proof”
This feature is mainly useful for writing functions with dependent types
In Coq - you do as much as ML/Haskell when you can…? Unlike Agda - you program intensively in dependent type…?
When Extracting to OCaml…Coq did a lot of Object.magic for coercion to bypass OCaml type system. (Coq has maken sure the type safety.)
Logical Connectives as Inductive Types
Inductive definitions are powerful enough to express most of the connectives we have seen so far. Indeed, only universal quantification (with implication as a special case) is built into Coq; all the others are defined inductively. Wow…
CoqI: What’s Coq logic? Forall + Inductive type (+ coinduction), that’s it.
Conjunctions
1 2 3 4 5 6 7
Inductive and (P Q : Prop) : Prop := | conj : P → Q → and P Q.
Print prod. (* ===> Inductive prod (X Y : Type) : Type := | pair : X -> Y -> X * Y. *)
similar to prod (product) type… more connections happening here.
This similarity should clarify why destruct and intros patterns can be used on a conjunctive hypothesis.
Similarly, the split tactic actually works for any inductively defined proposition with exactly one constructor (so here, apply conj, which will match the conclusion and generate two subgoal from assumptions )
A very direct proof:
1 2 3 4
Definition and_comm'_aux P Q (H : P ∧ Q) : Q ∧ P := match H with | conj HP HQ ⇒ conj HQ HP end.
Disjunction
1 2 3
Inductive or (P Q : Prop) : Prop := | or_introl : P → or P Q | or_intror : Q → or P Q.
this explains why destruct works but split not..
Q&A - Slide 22 + 24
Both Question asked about what’s the type of some expression
1 2 3 4 5 6 7 8 9 10
fun P Q R (H1: and P Q) (H2: and Q R) ⇒ match (H1,H2) with | (conj__ HP _, conj ___ HR) ⇒ conj P R HP HR end.
fun P Q H ⇒ match H with | or_introl HP ⇒ or_intror Q P HP | or_intror HQ ⇒ or_introl Q P HQ end.
But if you simply Check on them, you will get errors saying: Error: The constructor conj (in type and) expects 2 arguments. or Error: The constructor or_introl (in type or) expects 2 arguments..
Coq Magics, “Implicit” Implicit and Overloading??
So what’s the problem? Well, Coq did some magics…
1 2 3 4
Print and. (* ===> *) Inductive and (A B : Prop) : Prop := conj : A -> B -> A /\ B For conj: Arguments A, B are implicit
constructor conj has implicit type arg w/o using {} in and …
1 2 3 4 5 6 7 8 9 10 11
Inductive or (A B : Prop) : Prop := or_introl : A -> A \/ B | or_intror : B -> A \/ B
For or_introl, when applied to no more than 1 argument: Arguments A, B are implicit For or_introl, when applied to 2 arguments: Argument A is implicit For or_intror, when applied to no more than 1 argument: Arguments A, B are implicit For or_intror, when applied to 2 arguments: Argument B is implicit
this is even more bizarre… constructor or_introl (and or_intror) are overloaded!! (WTF)
And the questions’re still given as if they’re inside the modules we defined our plain version of and & or (w/o any magics), thus we need _ in the positions we instantiate and & or so Coq will infer.
Existential Quantification
To give evidence for an existential quantifier, we package a witness x together with a proof that x satisfies the property P:
1 2 3 4 5 6 7
Inductive ex {A : Type} (P : A → Prop) : Prop := | ex_intro : ∀x : A, P x → ex P.
Check ex. (* ===> *) : (?A -> Prop) -> Prop Check even. (* ===> *) : nat -> Prop(* ?A := nat *) Check ex even. (* ===> *) : Prop Check ex (fun n => even n) (* ===> *) : Prop(* same *)
one interesting fact is, outside of our module, the built-in Coq behaves differently (magically):
1 2 3
Check ev. (* ===> *) : ∀ (A : Type), (A -> Prop) -> Prop Check even. (* ===> *) : nat -> Prop(* A := nat *) Check ex (fun n => even n) (* ===> *) : ∃ (n : nat) , even n : Prop(* WAT !? *)
A example of explicit proof object (that inhabit this type):
1 2
Definition some_nat_is_even : ∃n, even n := ex_intro even 4 (ev_SS 2 (ev_SS 0 ev_0)).
the ex_intro take even first then 4…not sure why the order becomes this…
1
Check (ex_intro). (* ===> *) : forall (P : ?A -> Prop) (x : ?A), P x -> ex P
To prove ex P, given a witness x and a proof of P x. This desugar to ∃ x, P x
the P here, is getting applied when we define prop ∃ x, P x.
but the x is not mentioned in type constructor…so it’s a existential type.
I don’t know why languages (including Haskell) use forall for existential tho.
exists tactic = applying ex_intro
True and False
1 2 3 4 5
Inductive True : Prop := | I : True.
(* with 0 constructors, no way of presenting evidence for False *) Inductive False : Prop := .
Equality
1 2 3 4 5 6
Inductive eq {X:Type} : X → X → Prop := | eq_refl : ∀x, eq x x.
Notation"x == y" := (eq x y) (at level 70, no associativity) : type_scope.
given a set X, it defines a family of propositions “x is equal to y,”, indexed by pairs of values (x and y) from X.
Can we also use it to construct evidence that 1 + 1 = 2? Yes, we can. Indeed, it is the very same piece of evidence!
The reason is that Coq treats as “the same” any two terms that are convertible according to a simple set of computation rules.
nothing in the unification engine but we relies on the reduction engine.
Q: how much is it willing to do? Mtf: just run them! (since Coq is total!)
The reflexivity tactic is essentially just shorthand for apply eq_refl.
Slide Q & A
(4) has to be applicable thing, i.e. lambda, or “property” in the notion!
In terms of provability of reflexivity
1 2
(fun n => S (S n)) = (fun n => 2 + n) (* reflexivity *) (fun n => S (S n)) = (fun n => n + 2) (* rewrite add_com *)
Inversion, Again
We’ve seen inversion used with both equality hypotheses and hypotheses about inductively defined propositions. Now that we’ve seen that these are actually the same thing
In general, the inversion tactic…
take hypo H whose type P is inductively defined
for each constructor C in P
generate new subgoal (assume H was built with C)
add the arguments (i.e. evidences of premises) of C as extra hypo (to the context of subgoal)
(apply constructor theorem), match the conclusion of C, calculates a set of equalities (some extra restrictions)
adds these equalities
if there is contradiction, discriminate, solve subgoal.
Q
Q: Can we write + in a communitive way? A: I don’t believe so.
每次我们使用 Inductive 来声明数据类型时,Coq 会自动为这个类型生成 _归纳原理_。 Every time we declare a new Inductive datatype, Coq automatically generates an induction principle for this type.
自然数的归纳原理:
1 2 3 4 5 6
Check nat_ind. :
∀ P : nat → Prop, P 0 → (∀ n : nat, P n -> P (S n)) → ∀ n : nat, P n
written as inference rule:
P 0
∀ n : nat, P n -> P (S n)
-------------------------
∀ n : nat, P n
induction tactic is wrapper of apply t_ind
Coq 为每一个 Inductive 定义的数据类型生成了归纳原理,包括那些非递归的 Coq generates induction principles for every datatype defined with Inductive, including those that aren’t recursive.
尽管我们不需要使用归纳来证明非递归数据类型的性质 Although of course we don’t need induction to prove properties of non-recursive datatypes. (destruct would be sufficient)
归纳原理的概念仍然适用于它们: 它是一种证明一个对于这个类型所有值都成立的性质的方法。 the idea of an induction principle still makes sense for them: it gives a way to prove that a property holds for all values of the type.
Non-recursive
1 2 3 4 5 6 7 8 9
Inductive yesno : Type := | yes | no.
Check yesno_ind. : yesno_ind : ∀ P : yesno → Prop, P yes → P no → ∀ y : yesno, P y
Check natlist_ind. : natlist_ind : ∀ P : natlist → Prop, P nnil → (∀ (n : nat) (l : natlist), P l -> P (ncons n l)) → ∀ l : natlist, P l
P nnil
∀ (n : nat) (l : natlist), P l -> P (ncons n l)
-----------------------------------------------
∀ l : natlist, P l
P only need to fullfill l : the_type but not n:nat since we are proving property of the_type.
The Pattern
These generated principles follow a similar pattern.
induction on each cases
proof by exhaustiveness?
1 2 3 4 5 6 7 8 9 10
Inductive t : Type := | c1 (x1 : a1) ... (xn : an) ... | cn ...
t_ind : ∀P : t → Prop, ... casefor c1 ... → ... casefor c2 ... → ... ... casefor cn ... → ∀n : t, P n
对于 t 的归纳原理是又所有对于 c 的归纳原理所组成的: (即所有 case 成立)
对于 c 的归纳原理则是
对于所有的类型为 a1...an 的值 x1...xn,如果 P 对每个 归纳的参数(每个具有类型 t 的 xi)都成立,那么 P 对于 c x1 ... xn 成立”
每个具有类型 t 的参数的地方即发生了「递归」与「子结构」,归纳假设 = 「对子结构成立」.
Polymorphism
接下来考虑多态列表:
1 2 3 4 5 6 7 8 9
(* in ADT syntax *) Inductive list (X:Type) : Type := | nil | cons (x : X) (l': list X)
(* in GADT syntax *) Inductive list (X:Type) : Type := | nil : list X | cons : X → list X → list X.
here, the whole def is parameterized on a set X: that is, we are defining a family of inductive types list X, one for each X.
这里,整个定义都是被集合 X参数化_的: 也即,我们定义了一个族 list : X -> Type, 对于每个 X,我们都有一个对应的_项: list X, which is a Type, 可写作 list X : Type.
list_ind can be thought of as a polymorphic function that, when applied to a type X, gives us back an induction principle specialized to the type list X.
因此,其归纳定理 list_ind 是一个被 X 参数化多态的函数。 当应用 X : Type 时,返回一个特化在 list X : Type 上的归纳原理
1 2 3 4
list_ind : ∀(X : Type) (P : list X → Prop), P [] → (∀(x : X) (l : list X), P l → P (x :: l)) → ∀l : list X, P l
∀(X : Type), {
P [] -- base structure holds
∀(x : X) (l : list X), P l → P (x :: l) -- sub-structure holds -> structure holds
---------------------------------------
∀l : list X, P l -- all structure holds
}
Induction Hypotheses 归纳假设
The induction hypothesis is the premise of this latter implication — the assumption that P holds of n', which we are allowed to use in proving that P holds for S n'.
归纳假设就是 P n' -> P (S n') 这个蕴含式中的前提部分 使用 nat_ind 时需要显式得用 intros n IHn 引入,于是就变成了 proof context 中的假设.
More on the induction Tactic
“Re-generalize” 重新泛化
Noticed that in proofs using nat_ind, we need to keep n generailzed. if we intros particular n first then apply nat_ind, it won’t works…
But we could intros n. induction n., that’s induction tactic internally “re-generalize” the n we perform induction on.
Automatic intros i.e. specialize variables before the variable we induction on
A canonical case is induction n vs induction m on theorem plus_comm'' : ∀n m : nat, n + m = m + n.. to keep a var generial…we can either change variable order under ∀, or using generalize dependent.
Induction Principles in Prop
理解依赖类型的归纳假设 与 Coq 排除证据参数的原因
除了集合 Set,命题 Prop 也可以是归纳定义与 induction on 得. 难点在于:Inductive Prop 通常是 dependent type 的,这里会带来复杂度。
考虑命题 even:
1 2 3
Inductive even : nat → Prop := | ev_0 : even 0 | ev_SS : ∀n : nat, even n → even (S (S n)).
我们可以猜测一个最 general 的归纳假设:
1 2 3 4
ev_ind_max : ∀ P : (∀n : nat, even n → Prop), P O ev_0 → (∀(m : nat) (E : even m), P m E → P (S (S m)) (ev_SS m E)) → ∀(n : nat) (E : even n), P n E
即:
P 0 ev_0 -- base
∀(m : nat) (E : even m), P m E → P (S (S m)) (ev_SS m E) -- sub structure -> structure
--------------------------------------------------------
∀(n : nat) (E : even n), P n E -- all structure
注意这里:
even is indexed by nat n (对比 list is parametrized by X)
从族的角度: even : nat -> Prop, a family of Prop indexed by nat
从实体角度: 每个 E : even n 对象都是一个 evidence that particular nat is even.
要证的性质 P is parametrized by E : even n 也因此连带着 by n. 也就是 P : (∀n : nat, even n → Prop) (对比 P : list X → Prop)
所以其实关于 even n 的性质是同时关于数字 n 和证据 even n 这两件事的.
因此 sub structure -> structure 说得是:
whenever n is an even number and E is an evidence of its evenness, if P holds of n and E, then it also holds of S (S n) and ev_SS n E. 对于任意数字 n 与证据 E,如果 P 对 n 和 E 成立,那么它也对 S (S n) 和 ev_SS n E 成立。
然而,当我们 induction (H : even n) 时,我们通常想证的性质并不包括「证据」,而是「满足该性质的这 Type 东西」的性质, 比如:
nat 上的一元关系 (性质) 证明 nat 的性质 : ev_even : even n → ∃k, n = double k
nat 上的二元关系 证明 nat 上的二元关系 : le_trans : ∀m n o, m ≤ n → n ≤ o → m ≤ o
二元关系 reg_exp × list T 证明 二元关系 reg_exp × T: in_re_match : ∀T (s : list T) (x : T) (re : reg_exp), s =~ re → In x s → In x (re_chars re). 都是如此,
因此我们也不希望生成的归纳假设是包括证据的… 原来的归纳假设:
∀P : (∀n : nat, even n → Prop),
... →
∀(n : nat) (E : even n), P n E
可以被简化为只对 nat 参数化的归纳假设:
∀P : nat → Prop,
... →
∀(n : nat) (E: even n), P n
因此 coq 生成的归纳原理也是不包括证据的。注意 P 丢弃了参数 E:
1 2 3 4
even_ind : ∀ P : nat -> Prop, P 0 → (∀ n : nat, even n -> P n -> P (S (S n))) → ∀ n : nat, even n -> P n *)
用人话说就是:
P 对 0 成立,
对任意 n,如果 n 是偶数且 P 对 n 成立,那么 P 对 S (S n) 成立。 => P 对所有偶数成立
“General Parameter”
1 2 3
Inductive le : nat → nat → Prop := | le_n : ∀ n, le n n | le_S : ∀ n m, (le n m) → (le n (S m)).
1 2 3
Inductive le (n:nat) : nat → Prop := | le_n : le n n | le_S m (H : le n m) : le n (S m).
两者虽然等价,但是共同的 ∀ n 可以被提升为 typecon 的参数, i.e. “General Parameter” to the whole definition.
其生成的归纳假设也会不同: (after renaming)
1 2 3 4
le_ind : ∀ P : nat -> nat -> Prop, (∀ n : nat, P n n) -> (∀ n m : nat, le n m -> P n m -> P n (S m)) -> ∀ n m : nat, le n m -> P n m
1 2 3 4
le_ind : ∀ (n : nat) (P : nat -> Prop), P n -> (∀ m : nat, n <= m -> P m -> P (S m)) -> ∀ m : nat, n <= m -> P m
The 1st one looks more symmetric but 2nd one is easier (for proving things).
a possible connection between the components of a k-tuple.
I have been long confused with Unary Relations vs. Binary Relation on the Same Set (homogeneous relation) I thought they were same…but turns out they are totally different!
Unary/1-place relation is Predicate or Property!
Either defined via set X ⊆ P or x ∈ P, or defined via function P : X -> Bool or P : X -> {⊥, ⊤}. (usually used in Math. Logic)
totally different with total function but ask the binary relation holds between every pair.
Reflexive
1 2
Definition transitive {X: Type} (R: relation X) := ∀a b c : X, (R a b) → (R b c) → (R a c).
Transitive
1 2
Definition transitive {X: Type} (R: relation X) := ∀a b c : X, (R a b) → (R b c) → (R a c).
Symmetric & Antisymmetric
1 2 3 4 5
Definition symmetric {X: Type} (R: relation X) := ∀a b : X, (R a b) → (R b a). Definition antisymmetric {X: Type} (R: relation X) := ∀a b : X, (R a b) → (R b a) → a = b.
Antisymmetric vs Asymmetric vs Non-symmetric (反对称 vs. 非对称 vs. 不-对称)
A relation is asymmetric if and only if it is both antisymmetric and irreflexive e.g. <= is neither symmetric nor asymmetric, but it’s antisymmetric… 反对称: 可以自反 (只能 reflexive 时对称) <= 非对称: 不能自反 < 不对称: 不是对称
A partial order under which every pair of elements is comparable is called a total order or linear order In the Coq standard library it’s called just order for short:
Def. smallest transitive relation on X containing R.
Operationally, as a + operator on a binary relation R:
R+ = R ∪ { (x1,xn) | n > 1 ∧ (x1,x2), ..., (xn-1,xn) ∈ R }
We can also constructively and inductively definition using R^i where i = i-transitivity away.
Reflexive, Transitive Closure
R* = R⁼ ∪ R+
Why is it useful?
The idea is that a relation is extended s.t. the derived relation has the (reflexsive and) transitive property. – Prof. Arthur
e.g. the “descendant” relation is the transitive closure of the “child” relation, the “derives-star (⇒⋆)” relation is the reflexive-transitive closure of the “derives (⇒)” relation. the “ε-closure” relation is the reflexive-transitive closure of the “ε-transition” relation. the “Kleene-star (Σ⋆)” relation is the reflexive-transitive closure of the “concatentation” relation.
Another way is to think them as “set closed under some operation”.
Back to Coq
1 2 3 4 5 6 7
Inductive clos_refl_trans {A: Type} (R: relation A) : relation A := | rt_step x y (H : R x y) : clos_refl_trans R x y (** original relation **) | rt_refl x : clos_refl_trans R x x (** reflexive xRx **) | rt_trans x y z (** transitive xRy ∧ yRz → xRz **) (Hxy : clos_refl_trans R x y) (Hyz : clos_refl_trans R y z) : clos_refl_trans R x z.
The above version will generate 2 IHs in rt_trans case. (since the proof tree has 2 branches).
Here is a better “linked-list”-ish one. (we will exclusively use this style)
1 2 3 4 5 6
Inductive clos_refl_trans_1n {A : Type} (R : relation A) (x : A) : A → Prop := | rt1n_refl : clos_refl_trans_1n R x x | rt1n_trans (y z : A) (Hxy : R x y) (Hrest : clos_refl_trans_1n R y z) : clos_refl_trans_1n R x z.
In later chapter, we will define a decorator multi that can take any binary relation on a set and return its closure relation:
1 2 3
Inductive multi (X : Type) (R : relation X) : relation X := | multi_refl : forall x : X, multi R x x | multi_step : forall x y z : X, R x y -> multi R y z -> multi R x z
We name it step, standing for doing one step of this relation, and then we still have the rest (sub-structure) satisfied the closure relation.