04 - Inductive Propositions, Relations, and Curry-Howard
Inductive Propositions, Relations, and Curry-Howard
References
- https://softwarefoundations.cis.upenn.edu/lf-current/IndProp.html
- https://softwarefoundations.cis.upenn.edu/lf-current/ProofObjects.html
Recap
- Last time we introduced the language of propositions in Coq. Propositions enable us to express logical claims. These claims could either by True or False
- We then saw how we could use the proof system in Coq to prove if a proposition was True or False.
- Finally, we saw that what distinguished a proposition from booleans was the notion of *decidability*. A general proposition does not satisfy law of excluded middle whereas a decidable proposition does. This naturally led us to consider the constructive nature of Coq.
- Today will be the final part of our crash course into Coq before turning our attention to survey various computational models (machine + language).
Goal for today
- Introduction to inductively defined propositions
- Induction and proof trees
- Arithmetic expression language + operational semantics.
- Operational semantics for arithmetic expression language.
- Introduce the Curry-Howard isomorphism.
- Equality as an inductive type.
(* ================================================================= *)
Even as an Inductively Defined Propositions
- We already saw that we could define inductive data-types
Inductive nat : Set := | O: nat | S: nat -> nat
- We also saw two ways to define evenness on natural numbers.
Fixpoint is_evenb (n: nat): bool := match n with | O => true | S O => false | S (S n') => is_evenb n' end.
Fixpoint is_even (n: nat): Prop := match n with | O => True | S O => False | S (S n') => is_even n' end.
Definition is_even_ex (n: nat): Prop := exists m: nat, double m = n.
- Now we'll see how we can use inductive definitions to define evenness.
- This will be our first example of an inductively defined proposition.
- SEAR THIS EXAMPLE INTO YOUR MEMORY. We will use this over and over (and over) again.
Inductive ev' : nat -> Prop :=
| ev'_0 : ev' 0
| ev'_SS : forall (n: nat) (H: ev' n), ev' (S (S n)).
- ev' is an inductive type that takes a nat and returns a prop.
- ev'_0 is a constructor that returns the proposition ev' 0. We can read this as "I assert that 0 is even".
- ev'_SS is a constructor that returns the proposition forall (n : nat) (H : ev' n), ev' (S (S n)). We can read this as "Give me a natural number n and a proof that ev' n holds and I'll assert that ev' (S (S n)) holds."
- Here's another way to write it.
Inductive ev : nat -> Prop :=
| ev_0 : ev 0
(* different syntax for same concept *)
| ev_SS (n : nat) (H : ev n) : ev (S (S n)).
- Question: why can't we write the following similar to list?
Fail Inductive wrong_ev (n : nat) : Prop :=
| wrong_ev_0 : wrong_ev 0
| wrong_ev_SS (H: wrong_ev n) : wrong_ev (S (S n)).
| wrong_ev_0 : wrong_ev 0
| wrong_ev_SS (H: wrong_ev n) : wrong_ev (S (S n)).
- Writing (n: nat) makes it a *global parameter* which means that the argument to wrong_ev has to be the same in all cases.
- Writing nat -> prop makes it an *index* which means that the argument to wrong_ev can vary in each case. This notion of an index will be important for talking about dependent types.
- Question: why did we not write ev_SS: nat -> ev n -> ev (S (S n))?
Fail Inductive wrong_ev' : nat -> Prop :=
| wrong_ev'_0: wrong_ev' 0
| wrong_ev'_SS : nat -> wrong_ev' n -> wrong_ev' (S (S n)).
- Recall that "A -> B" is shorthand for forall _: A, B.
- In this case, we need the number "n".
- This is an example of a dependent type, and we will discuss this more when we talk about the Curry-Howard isomorphism.
Theorem ev_4:
ev 4.
Proof.
apply ev_SS.
apply ev_SS.
apply ev_0. (* Question: forwards or backwards reasoning? *)
Qed.
Theorem ev_plus4 :
forall (n: nat), ev n -> ev (4 + n).
Proof.
intros n.
simpl.
intros Hn.
apply ev_SS.
apply ev_SS.
apply Hn.
Qed.
Paper-Pencil Inference Rules
- What's the mental model for the proofs above?
- Let's take a detour to paper-pencil *inference rules*, i.e., how we would do this without a proof assistant.
Inductively defined sets
(*
We can define _inductively defined sets_ as follows:
(Axiom)
(empty premise)
--------------- (name of rule)
a \in S (conclusion)
This axiom asserts that the element a is always in the set S.
(Inference Rule)
a_1 \in S .... a_n \in S (premise)
-------------------------- (name of rule)
a \in S (conclusion)
This inference rule asserts that if we can show that a_1 \in S, a_n \in S,
then we can show that a \in S.
*)
(*
Suppose we are trying to define the set of natural numbers nat.
(Axiom)
--------------- O
O \in nat
(Inference rule)
n \in nat
------------------ S
S n \in nat
*)
Print nat.
(*
Suppose we are trying to define the set of lists of type X.
(Axiom)
--------------- nil
{} \in list(X)
(Inference rule)
x \in X ls \in list(X)
------------------------- cons
{x, ls} \in list(X)
*)
Print list.
(*
(Axiom)
--------------- ev_O
O \in ev
(Inference rule)
n \in ev
------------------ ev_SS
S (S (n)) \in ev
*)
Print ev.
(* notice we have Prop now! *)
Theorem ev_4': ev 4.
Proof.
apply (ev_SS 2 (ev_SS 0 ev_0)).
Qed.
(*
------ ev_0
ev O
----------------- ev_SS
ev (S (S (O)))
------------------------ ev_SS
ev (S (S (S (S O))))
*)
- Question: why is this a tree?
- Answer: eventually we may have rules that require multiple premises to be satisfied, in which case we will eventually get a tree out.
- We will see an example of this later on.
Working with Inductive Propositions
Inversion on evidence
Theorem ev_inversion :
forall (n: nat),
ev n ->
(n = 0) \/ (exists n', n = S (S n') /\ ev n').
Proof.
intros n E.
(* Just like destruct on natural numbers or lists. *)
destruct E.
- (* E = ev_0 *)
left.
reflexivity.
- (* E = ev_SS n *)
right.
exists n.
split.
reflexivity.
apply E.
Qed.
- Theorems of this form are called inversion lemmas.
- Intuitively, it is saying that if we build a proof of ev n, it must be constructed with informat either either from the premises of ev_O or ev_SS.
- What properties of inductive types does this remind you of?
Theorem evSS_ev :
forall (n: nat),
ev (S (S n)) -> ev n.
Proof.
intros n E.
inversion E. (* tactic inversion applies the inversion lemma and
removes trivial cases. For example, we know we cannot
be in the ev_O case. Why? *)
apply H0.
Qed.
Induction on evidence
- Recall that all inductive types have an induction principle.
- The same is true of inductive propositions.
- We'll need some definitions from our previous lecture.
- You can run coqc lec_03_proof.v to compile the previous lecture.
- Now we can use it.
Require Import lec_03_proof.
Print lec_03_proof.
Lemma ev_even_ex :
forall (n: nat),
ev n -> is_even_ex n.
Proof.
intros n E.
(* We are performing induction on the evidence! *)
induction E as [|n' E' IH].
- (* E = ev_0 *)
unfold is_even_ex.
exists 0.
reflexivity.
- (* E = ev_SS n' E' with IH : Even E' *)
unfold is_even_ex in IH.
destruct IH as [k Hk].
unfold is_even_ex.
exists (S k). simpl.
rewrite Hk.
reflexivity.
Qed.
Print lec_03_proof.
Lemma ev_even_ex :
forall (n: nat),
ev n -> is_even_ex n.
Proof.
intros n E.
(* We are performing induction on the evidence! *)
induction E as [|n' E' IH].
- (* E = ev_0 *)
unfold is_even_ex.
exists 0.
reflexivity.
- (* E = ev_SS n' E' with IH : Even E' *)
unfold is_even_ex in IH.
destruct IH as [k Hk].
unfold is_even_ex.
exists (S k). simpl.
rewrite Hk.
reflexivity.
Qed.
(*
-------------- ev_O or ev_SS
ev n
----------------------------
exists k, double k = n
Case ev_O:
-------------- ev_O
ev O
---------------------------
exists k, double k = O
Case ev_SS:
------ -----------------------
ev n' exists m, double m = n'
----------------------------------- IH
exists k, double k = S (S n')
*)
Inductive le : nat -> nat -> Prop :=
| le_n (n : nat) : le n n
| le_S (n m : nat) (H : le n m) : le n (S m).
Notation "n <= m" := (le n m).
Theorem test_le1 :
3 <= 3.
Proof.
apply le_n.
Qed.
(*
----------- le_n
3 <= 3
*)
Theorem test_le2 :
3 <= 6.
Proof.
apply le_S.
apply le_S.
apply le_S.
apply le_n.
Qed.
----------- le_n
3 <= 3
*)
Theorem test_le2 :
3 <= 6.
Proof.
apply le_S.
apply le_S.
apply le_S.
apply le_n.
Qed.
(*
----------- le_n
3 <= 3
----------- le_S
3 <= 4
----------- le_S
3 <= 5
----------- le_S
3 <= 6
*)
Theorem test_le3 :
(2 <= 1) -> 2 + 2 = 5.
Proof.
intros H.
inversion H.
inversion H2.
Qed.
----------- le_n
3 <= 3
----------- le_S
3 <= 4
----------- le_S
3 <= 5
----------- le_S
3 <= 6
*)
Theorem test_le3 :
(2 <= 1) -> 2 + 2 = 5.
Proof.
intros H.
inversion H.
inversion H2.
Qed.
(*
----------- (contradiction, neither le_n nor le_S apply, so we can't build this)
2 <= 0
----------- le_S
2 <= 1
----------------------- intros
2 <= 1 -> 2 + 2 = 5
*)
----------- (contradiction, neither le_n nor le_S apply, so we can't build this)
2 <= 0
----------- le_S
2 <= 1
----------------------- intros
2 <= 1 -> 2 + 2 = 5
*)
Arithmetic Expression Language
- Let's define a grammar for a small arithmetic language.
- You may think of this as a simple calculator language.
Inductive exp : Set :=
| nat_exp: nat -> exp
| plus_exp: exp -> exp -> exp
| times_exp: exp -> exp -> exp.
Print exp_ind.
(*
----------- nat_exp
n \in exp
e1 \in exp e2 \in exp
------------------------ plus_exp
e1 + e2 \in exp
e1 \in \exp e2 \in exp
------------------------ times_exp
e1 * e2 \in exp
*)
----------- nat_exp
n \in exp
e1 \in exp e2 \in exp
------------------------ plus_exp
e1 + e2 \in exp
e1 \in \exp e2 \in exp
------------------------ times_exp
e1 * e2 \in exp
*)
- Right now it's just a bunch of syntax.
- What about its semantics?
- Let's define it as an inductive relation!
Inductive exp_step : exp -> exp -> Prop :=
| plus_reduce :
forall (n1 n2: nat),
exp_step (plus_exp (nat_exp n1) (nat_exp n2)) (nat_exp (n1 + n2))
| plus_reduce_left :
forall (e1 e2: exp),
forall (e1': exp), exp_step e1 e1' ->
exp_step (plus_exp e1 e2) (plus_exp e1' e2)
| plus_reduce_right :
forall (n: nat) (e2: exp),
forall (e2': exp), exp_step e2 e2' ->
exp_step (plus_exp (nat_exp n) e2) (plus_exp (nat_exp n) e2')
| times_reduce :
forall (n1 n2: nat),
exp_step (times_exp (nat_exp n1) (nat_exp n2)) (nat_exp (n1 * n2))
| times_reduce_left :
forall (e1 e2: exp),
forall (e1': exp), exp_step e1 e1' ->
exp_step (times_exp e1 e2) (times_exp e1' e2)
| times_reduce_right :
forall (n: nat) (e2: exp),
forall (e2': exp), exp_step e2 e2' ->
exp_step (times_exp (nat_exp n) e2) (times_exp (nat_exp n) e2').
Print exp_step_ind.
(*
---------------------- plus_reduce
"n1 + n2" -> n1 + n2
e1 -> e1'
------------------------ plus_reduce_left
"e1 + e2" -> "e1' + e2"
e2 -> e2'
------------------------ plus_reduce_right
"n + e2" -> "n + e2'"
---------------------- times_reduce
"n1 * n2" -> n1 * n2
e1 -> e1'
------------------------ times_reduce_left
"e1 * e2" -> "e1' * e2"
e2 -> e2'
------------------------ times_reduce_right
"n * e2" -> "n * e2'"
*)
---------------------- plus_reduce
"n1 + n2" -> n1 + n2
e1 -> e1'
------------------------ plus_reduce_left
"e1 + e2" -> "e1' + e2"
e2 -> e2'
------------------------ plus_reduce_right
"n + e2" -> "n + e2'"
---------------------- times_reduce
"n1 * n2" -> n1 * n2
e1 -> e1'
------------------------ times_reduce_left
"e1 * e2" -> "e1' * e2"
e2 -> e2'
------------------------ times_reduce_right
"n * e2" -> "n * e2'"
*)
- Our first property of the step relation is progress: either we reduce to a natural number or there is a step that we can take.
- We will prove this by induction on the structure of the expression exp.
Theorem exp_progress:
forall (e: exp),
(exists (n: nat), e = nat_exp n) \/ exists e', exp_step e e'.
Proof.
induction e.
{ (* nat_exp *)
left.
exists n.
reflexivity.
}
{ (* plus_exp *)
destruct IHe1.
destruct IHe2.
- right.
destruct H.
destruct H0.
rewrite H.
rewrite H0.
exists (nat_exp (x + x0)).
apply plus_reduce.
- right.
destruct H.
destruct H0.
rewrite H.
exists (plus_exp (nat_exp x) x0).
apply plus_reduce_right.
apply H0.
- destruct H.
destruct IHe2.
right.
destruct H0.
exists (plus_exp x e2).
apply plus_reduce_left.
apply H.
right.
destruct H0.
exists (plus_exp x e2).
apply plus_reduce_left.
apply H.
}
{
destruct IHe1.
destruct IHe2.
- right.
destruct H.
destruct H0.
rewrite H.
rewrite H0.
exists (nat_exp (x * x0)).
apply times_reduce.
- right.
destruct H.
destruct H0.
rewrite H.
exists (times_exp (nat_exp x) x0).
apply times_reduce_right.
apply H0.
- destruct H.
destruct IHe2.
right.
destruct H0.
exists (times_exp x e2).
apply times_reduce_left.
apply H.
right.
destruct H0.
exists (times_exp x e2).
apply times_reduce_left.
apply H.
}
Qed.
- You might have noticed that we could have given alternative semantics.
- In particular, why bother with an inductive proposition at all?
- Here's a sneak peak.
Fixpoint eval_exp (e: exp) : option exp :=
match e with
| nat_exp n => Some (nat_exp n)
| plus_exp e1 e2 =>
match eval_exp e1, eval_exp e2 with
| Some (nat_exp n1), Some (nat_exp n2) => Some (nat_exp (n1 + n2))
| _, _ => None
end
| times_exp e1 e2 =>
match eval_exp e1, eval_exp e2 with
| Some (nat_exp n1), Some (nat_exp n2) => Some (nat_exp (n1 * n2))
| _, _ => None
end
end.
- We might be interested in proving that such semantics are equivalent.
- We'll save this for some other time.
The Curry Howard Correspondence
The analogy:
- A proof script is a more high-level way to build up a term of that type.
Theorem ev_4'' : ev 4.
Proof.
Show Proof.
apply ev_SS.
Show Proof.
apply ev_SS.
Show Proof.
apply ev_0.
Show Proof.
Qed.
Theorem ev_plus4' :
forall n,
ev n -> ev (4 + n).
Proof.
intros n H.
simpl.
apply ev_SS.
apply ev_SS.
apply H.
Qed.
Definition ev_plus4'': forall (n: nat), ev n -> ev (4 + n) :=
fun (n : nat) => fun (H : ev n) =>
ev_SS (S (S n)) (ev_SS n H).
Print prod.
Theorem proj1' :
forall P Q,
P /\ Q -> P.
Proof.
intros P Q HPQ.
destruct HPQ as [HP HQ].
apply HP.
Show Proof.
Qed.
Print conj.
Definition proj1'': forall P Q, P /\ Q -> P :=
fun (P Q: Prop) =>fun (HPQ: P /\ Q) =>
match HPQ with
| conj HP _ => HP
end.
Theorem inj_l' : forall (P Q : Prop), P -> P \/ Q.
Proof.
intros P Q HP. left. apply HP.
Show Proof.
Qed.
Definition inj_l : forall (P Q : Prop), P -> P \/ Q :=
fun P Q HP => or_introl HP.
Print ex_intro.
Check ex (fun n => ev n) : Prop.
Definition some_nat_is_even : exists n, ev n :=
ex_intro ev 4 (ev_SS 2 (ev_SS 0 ev_0)).
Print True.
Fact p_implies_true : forall P, P -> True.
Proof.
intros.
apply I.
Show Proof.
Qed.
Definition p_implies_true': forall P, P -> True :=
fun (P: Type) => fun (_ : P) => I.
Print False.
Fail Definition contra : False :=
0 = 1.
Definition false_implies_zero_eq_one: False -> 0 = 1 :=
fun contra => match contra with end.
- We might wonder if Coq's definition of equality is too restrictive.
- It turns out that it is (logically) equvialent to Leibniz equality.
Lemma equality_implies_leibniz_equality : forall (X : Type) (x y: X),
x = y -> forall P: X -> Prop, P x -> P y.
Proof.
intros.
rewrite <- H.
assumption.
Qed.
Lemma leibniz_equality_implies_equality : forall (X : Type) (x y: X),
(forall P: X -> Prop, P x -> P y) -> x = y.
Proof.
intros.
(* This part is the interesting bit of the proof *)
specialize (H (fun z => x = z)).
simpl in H.
apply H.
reflexivity.
Qed.
Summary
- We saw inductively defined propositions, induction, and proof trees.
- We introduced an arithmetic expression language and operational semantics.
- We saw the Curry-Howard isomorphism and equality as an inductive type.