CurryHowardA Formal Development of the Curry-Howard Correspondence
We will first develop the correspondence in a setting that focuses
on implication. Later we will add conjunction and disjunction.
Logic
Propositions
- An atom is an atomic proposition, much like P in P : Prop, as we
are accustomed to in Rocq. Atomic propositions are given names using
Rocq strings.
- An implication is the usual P → Q.
- The contradictory proposition is named false.
From that starting point, we can define negation and truth in terms
of other propositions.
Definition not (p : proposition) : proposition :=
implies p false.
Definition true : proposition :=
implies false false.
implies p false.
Definition true : proposition :=
implies false false.
Here are some examples of propositions.
Definition ex_P :=
(* P -> P *)
implies (atom "P") (atom "P").
Definition ex_PQ :=
(* P -> Q -> P *)
implies (atom "P") (implies (atom "Q") (atom "P")).
Definition ex_PQR :=
(* (P -> Q -> R) -> (P -> Q) -> P -> R *)
implies
(implies (atom "P") (implies (atom "Q") (atom "R")))
(implies (implies (atom "P") (atom "Q")) (implies (atom "P") (atom "R"))).
(* P -> P *)
implies (atom "P") (atom "P").
Definition ex_PQ :=
(* P -> Q -> P *)
implies (atom "P") (implies (atom "Q") (atom "P")).
Definition ex_PQR :=
(* (P -> Q -> R) -> (P -> Q) -> P -> R *)
implies
(implies (atom "P") (implies (atom "Q") (atom "R")))
(implies (implies (atom "P") (atom "Q")) (implies (atom "P") (atom "R"))).
Proofs
| (axiom) | |
| A, p ⊢ p |
| A, p ⊢ q | (implies intro) |
| A ⊢ p -> q |
| A ⊢ p -> q A ⊢ p | (implies elim) |
| A ⊢ q |
| A ⊢ false | (false elim) |
| A ⊢ p |
- Axiom: from a set of assumptions that includes p, it follows that
p is provable -- that is, p holds "by assumption."
- Implies introduction: if it is possible to prove q from the assumption
of A, p, then it follows that p → q is provable from the assumption
of A. That is, a proof of p → q is a way of transforming the
assumption of p into a proof of q. The name of the rule indicates
that the implication connective → is introduced in the conclusion;
it was not present in the premise.
- Implies elimination: if it is possible to prove p → q as well as p
from A, then it follows that q is provable from A. That is, since
there is a way of transforming an assumption of p into a proof of q,
and since there is a proof of p, it is possible to use those together
to construct a proof of q. The name of the rule indicates that →
is eliminated in the conclusion; it was present in the premises.
This rule is commonly known as "modus ponens".
- False elimination: if it is possible to prove false from A, then the set of assumptions is inconsistent, so it follows that it is possible to prove anything from A -- including an arbitrary proposition p. This is the "ex falso quodlibet" principle. Note that there is no inference rule for false introduction, since we should never be able to prove false from a consistent set of assumptions.
Inductive provable : list proposition → proposition → Prop :=
| axiom : ∀ A p,
In p A →
provable A p
| implies_intro : ∀ A p q,
provable (p :: A) q →
provable A (implies p q)
| implies_elim : ∀ A p q,
provable A (implies p q) →
provable A p →
provable A q
| false_elim : ∀ A p,
provable A false →
provable A p.
| axiom : ∀ A p,
In p A →
provable A p
| implies_intro : ∀ A p q,
provable (p :: A) q →
provable A (implies p q)
| implies_elim : ∀ A p q,
provable A (implies p q) →
provable A p →
provable A q
| false_elim : ∀ A p,
provable A false →
provable A p.
Here are some examples of using the proof system.
Example ex_P_provable : provable [] ex_P.
(*
-------------------- (axiom)
p ⊢ p
-------------------- (implies intro)
⊢ p -> p
*)
Proof.
unfold ex_P. apply implies_intro.
apply axiom. simpl. left. reflexivity.
Qed.
Example ex_PQ_provable : provable [] ex_PQ.
(*
------------------- (axiom)
P, Q ⊢ P
------------------- (implies intro)
P ⊢ Q -> P
------------------- (implies intro)
⊢ P -> Q -> P
*)
Proof.
(* WORKED IN CLASS *)
unfold ex_PQ. apply implies_intro. apply implies_intro.
apply axiom. simpl. right. left. reflexivity.
Qed.
Example ex_mp : ∀ (P Q : string),
provable [implies (atom P) (atom Q); (atom P)] (atom Q).
(*
------------------- (axiom) -------------- (axiom)
P -> Q, P ⊢ P -> Q P -> Q, P ⊢ P
---------------------------------------------- (implies elim)
P -> Q, P ⊢ Q
*)
Proof.
(* WORKED IN CLASS *)
intros P Q. apply implies_elim with (atom P).
- apply axiom. simpl. left. reflexivity.
- apply axiom. simpl. right. left. reflexivity.
Qed.
(*
-------------------- (axiom)
p ⊢ p
-------------------- (implies intro)
⊢ p -> p
*)
Proof.
unfold ex_P. apply implies_intro.
apply axiom. simpl. left. reflexivity.
Qed.
Example ex_PQ_provable : provable [] ex_PQ.
(*
------------------- (axiom)
P, Q ⊢ P
------------------- (implies intro)
P ⊢ Q -> P
------------------- (implies intro)
⊢ P -> Q -> P
*)
Proof.
(* WORKED IN CLASS *)
unfold ex_PQ. apply implies_intro. apply implies_intro.
apply axiom. simpl. right. left. reflexivity.
Qed.
Example ex_mp : ∀ (P Q : string),
provable [implies (atom P) (atom Q); (atom P)] (atom Q).
(*
------------------- (axiom) -------------- (axiom)
P -> Q, P ⊢ P -> Q P -> Q, P ⊢ P
---------------------------------------------- (implies elim)
P -> Q, P ⊢ Q
*)
Proof.
(* WORKED IN CLASS *)
intros P Q. apply implies_elim with (atom P).
- apply axiom. simpl. left. reflexivity.
- apply axiom. simpl. right. left. reflexivity.
Qed.
The use of the axiom rule followed by reasoning about In gets tedious.
We can automate it with the following little custom tactic. The techniques
used in the tactic will be explained in Auto (or alternatively
AltAuto).
Ltac by_assumption :=
constructor;
repeat match goal with
| ⊢ In ?p (?h :: ?t) ⇒ simpl
| ⊢ (?x = ?y) ∨ ?p ⇒ first [left; reflexivity | right]
| ⊢ False ⇒ idtac "by_assumption failed: not in assumptions"; fail
end.
Example in_assumptions : provable [atom "P"] (atom "P").
Proof.
by_assumption.
Qed.
Example not_in_assumptions : provable [atom "Q"] (atom "P").
Proof.
by_assumption.
Abort.
Example ex_P_provable' : provable [] ex_P.
Proof.
unfold ex_P. apply implies_intro.
by_assumption.
Qed.
constructor;
repeat match goal with
| ⊢ In ?p (?h :: ?t) ⇒ simpl
| ⊢ (?x = ?y) ∨ ?p ⇒ first [left; reflexivity | right]
| ⊢ False ⇒ idtac "by_assumption failed: not in assumptions"; fail
end.
Example in_assumptions : provable [atom "P"] (atom "P").
Proof.
by_assumption.
Qed.
Example not_in_assumptions : provable [atom "Q"] (atom "P").
Proof.
by_assumption.
Abort.
Example ex_P_provable' : provable [] ex_P.
Proof.
unfold ex_P. apply implies_intro.
by_assumption.
Qed.
Exercise: 2 stars, standard (ex_PQR_provable)
Theorem ex_PQR_provable_rocq : ∀ (P Q R : Prop),
(P → Q → R) → (P → Q) → P → R.
Proof.
(* FILL IN HERE *) Admitted.
(P → Q → R) → (P → Q) → P → R.
Proof.
(* FILL IN HERE *) Admitted.
Second, prove it using natural deduction.
Tip: try sketching out the derivation with the inference rules on paper
first. The tricky part is finding the correct propositions to instantiate
implies_elim with each time.
Theorem implies_trans : ∀ (P Q R : string),
provable []
(implies (implies (atom P) (atom Q))
(implies (implies (atom Q) (atom R))
(implies (atom P) (atom R)))).
Proof.
(* FILL IN HERE *) Admitted.
Theorem exfalso_quodlibet : ∀ (P : string),
provable [] (implies false (atom P)).
Proof.
(* FILL IN HERE *) Admitted.
Theorem contrapositive : ∀ (P Q : string),
provable []
(implies (implies (atom P) (atom Q))
(implies (not (atom Q)) (not (atom P)))).
Proof.
(* FILL IN HERE *) Admitted.
☐
provable []
(implies (implies (atom P) (atom Q))
(implies (implies (atom Q) (atom R))
(implies (atom P) (atom R)))).
Proof.
(* FILL IN HERE *) Admitted.
Theorem exfalso_quodlibet : ∀ (P : string),
provable [] (implies false (atom P)).
Proof.
(* FILL IN HERE *) Admitted.
Theorem contrapositive : ∀ (P Q : string),
provable []
(implies (implies (atom P) (atom Q))
(implies (not (atom Q)) (not (atom P)))).
Proof.
(* FILL IN HERE *) Admitted.
☐
Programs
Syntax
- A type variable stands for an unknown type, much like the X
in X : Type that we have used when defining polymorphic lists
in Rocq. We again use Rocq strings as identifiers.
- An arrow represents a function type, like t1 → t2 in Rocq.
Next, we model program expressions (aka terms).
- As usual we have variables.
- A function aka abstraction is like fun (x : t) ⇒ e in Rocq.
It binds a variable, explicitly states the type of that variable,
and has an expression as its body.
- An application applies a function to an argument, like e1 e2 in Rocq.
Inductive expr :=
| var (id : string)
| abs (id : string) (t : type) (e : expr)
| app (e1 e2 : expr).
| var (id : string)
| abs (id : string) (t : type) (e : expr)
| app (e1 e2 : expr).
Here are some examples of programs.
Definition e_I : expr :=
(* fun (x:T) => x *)
abs "x" (tvar "T") (var "x").
Definition e_K : expr :=
(* fun (x:T) (y:U) => x *)
abs "x" (tvar "T") (abs "y" (tvar "U") (var "x")).
Definition e_S : expr :=
(* fun (x:T->U->V) (y:T->U) (z:T) => x z (y z) *)
abs "x" (arrow (tvar "T") (arrow (tvar "U") (tvar "V")))
(abs "y" (arrow (tvar "T") (tvar "U"))
(abs "z" (tvar "T")
(app (app (var "x") (var "z"))
(app (var "y") (var "z"))))).
(* fun (x:T) => x *)
abs "x" (tvar "T") (var "x").
Definition e_K : expr :=
(* fun (x:T) (y:U) => x *)
abs "x" (tvar "T") (abs "y" (tvar "U") (var "x")).
Definition e_S : expr :=
(* fun (x:T->U->V) (y:T->U) (z:T) => x z (y z) *)
abs "x" (arrow (tvar "T") (arrow (tvar "U") (tvar "V")))
(abs "y" (arrow (tvar "T") (tvar "U"))
(abs "z" (tvar "T")
(app (app (var "x") (var "z"))
(app (var "y") (var "z"))))).
Type Checking
| (t-var) | |
| E, x : t ⊢ x : t |
| E, x : t1 ⊢ e : t2 | (t-abs) |
| E ⊢ fun (x : t1) => e : t1 -> t2 |
| E ⊢ e1 : t1 -> t2 E ⊢ e2 : t1 | (t-app) |
| E ⊢ e1 e2 : t2 |
- T-Var: if the typing environment binds x to t, then x has type t
in that environment.
- T-Abs: if e has type t2 in a typing environment that binds x to
t1, then the function fun (x : t1) ⇒ e, created by abstracting x,
has type t1 → t2.
- T-App: if e1 is a function with type t1 → t2 and e2 has type t1, then applying e1 to input e2 yields an output of type t2.
The type system inference rules are straightforward to formalize in Rocq.
Inductive hastype : tenv → expr → type → Prop :=
| t_var : ∀ E x t,
In (x, t) E →
hastype E (var x) t
| t_abs : ∀ E x t1 e t2,
hastype ((x, t1) :: E) e t2 →
hastype E (abs x t1 e) (arrow t1 t2)
| t_app : ∀ E e1 t1 t2 e2,
hastype E e1 (arrow t1 t2) →
hastype E e2 t1 →
hastype E (app e1 e2) t2.
| t_var : ∀ E x t,
In (x, t) E →
hastype E (var x) t
| t_abs : ∀ E x t1 e t2,
hastype ((x, t1) :: E) e t2 →
hastype E (abs x t1 e) (arrow t1 t2)
| t_app : ∀ E e1 t1 t2 e2,
hastype E e1 (arrow t1 t2) →
hastype E e2 t1 →
hastype E (app e1 e2) t2.
The example programs given above are all well-typed. Our by_assumption
tactic continues to work equally well for association lists.
Example e_I_typable :
hastype [] e_I (arrow (tvar "T") (tvar "T")).
(*
---------------------------- (t-var)
x : T ⊢ x : T
---------------------------- (t-abs)
⊢ fun (x : T) => x : T -> T
*)
Proof.
unfold e_I. apply t_abs. by_assumption.
Qed.
Example e_K_typable :
hastype [] e_K (arrow (tvar "T") (arrow (tvar "U") (tvar "T"))).
(*
------------------------------------------------ (t-var)
x : T, y : U ⊢ x : T
------------------------------------------------ (t-abs)
x : T ⊢ fun (y : U) => x : U -> T
------------------------------------------------ (t-abs)
⊢ fun (x : T) => fun (y : U) => x : T -> U -> T
*)
Proof.
(* WORKED IN CLASS *)
unfold e_K. apply t_abs. apply t_abs. by_assumption.
Qed.
hastype [] e_I (arrow (tvar "T") (tvar "T")).
(*
---------------------------- (t-var)
x : T ⊢ x : T
---------------------------- (t-abs)
⊢ fun (x : T) => x : T -> T
*)
Proof.
unfold e_I. apply t_abs. by_assumption.
Qed.
Example e_K_typable :
hastype [] e_K (arrow (tvar "T") (arrow (tvar "U") (tvar "T"))).
(*
------------------------------------------------ (t-var)
x : T, y : U ⊢ x : T
------------------------------------------------ (t-abs)
x : T ⊢ fun (y : U) => x : U -> T
------------------------------------------------ (t-abs)
⊢ fun (x : T) => fun (y : U) => x : T -> U -> T
*)
Proof.
(* WORKED IN CLASS *)
unfold e_K. apply t_abs. apply t_abs. by_assumption.
Qed.
Exercise: 2 stars, standard (e_S_typable)
Theorem e_S_typable :
hastype [] e_S
(arrow (arrow (tvar "T") (arrow (tvar "U") (tvar "V")))
(arrow (arrow (tvar "T") (tvar "U"))
(arrow (tvar "T") (tvar "V")))).
Proof.
(* FILL IN HERE *) Admitted.
☐
hastype [] e_S
(arrow (arrow (tvar "T") (arrow (tvar "U") (tvar "V")))
(arrow (arrow (tvar "T") (tvar "U"))
(arrow (tvar "T") (tvar "V")))).
Proof.
(* FILL IN HERE *) Admitted.
☐
Definition prog_B :=
(abs "g" (arrow (tvar "Y") (tvar "Z"))
(abs "f" (arrow (tvar "X") (tvar "Y"))
(abs "x" (tvar "X")
(app (var "g") (app (var "f") (var "x")))))).
Theorem prog_B_typable : ∃ (T : type),
hastype [] prog_B T.
Proof.
(* FILL IN HERE *) Admitted.
Definition prog_C :=
(abs "f" (arrow (tvar "X") (arrow (tvar "Y") (tvar "Z" )))
(abs "y" (tvar "Y")
(abs "x" (tvar "X")
(app (app (var "f") (var "x")) (var "y"))))).
Theorem prog_C_typable : ∃ (T : type),
hastype [] prog_C T.
Proof.
(* FILL IN HERE *) Admitted.
Definition prog_W_type :=
arrow (arrow (tvar "X") (arrow (tvar "X") (tvar "Y")))
(arrow (tvar "X") (tvar "Y")).
Theorem prog_W_typable : ∃ (e : expr),
hastype [] e prog_W_type.
Proof.
(* FILL IN HERE *) Admitted.
☐
(abs "g" (arrow (tvar "Y") (tvar "Z"))
(abs "f" (arrow (tvar "X") (tvar "Y"))
(abs "x" (tvar "X")
(app (var "g") (app (var "f") (var "x")))))).
Theorem prog_B_typable : ∃ (T : type),
hastype [] prog_B T.
Proof.
(* FILL IN HERE *) Admitted.
Definition prog_C :=
(abs "f" (arrow (tvar "X") (arrow (tvar "Y") (tvar "Z" )))
(abs "y" (tvar "Y")
(abs "x" (tvar "X")
(app (app (var "f") (var "x")) (var "y"))))).
Theorem prog_C_typable : ∃ (T : type),
hastype [] prog_C T.
Proof.
(* FILL IN HERE *) Admitted.
Definition prog_W_type :=
arrow (arrow (tvar "X") (arrow (tvar "X") (tvar "Y")))
(arrow (tvar "X") (tvar "Y")).
Theorem prog_W_typable : ∃ (e : expr),
hastype [] e prog_W_type.
Proof.
(* FILL IN HERE *) Admitted.
☐
The Correspondence
Arrow Elimination
| E ⊢ e1 : t1 -> t2 E ⊢ e2 : t1 | (t-app) |
| E ⊢ e1 e2 : t2 |
| A ⊢ p -> q A ⊢ p | (implies elim) |
| A ⊢ q |
| E ⊢ t1 -> t2 E ⊢ t1 | (t-app-erased) |
| E ⊢ t2 |
| A ⊢ p -> q A ⊢ p | (implies elim) |
| A ⊢ q |
Arrow Introduction
| E, x : t1 ⊢ e : t2 | (t-abs) |
| E ⊢ fun (x : t1) => e : t1 -> t2 |
| A, p ⊢ q | (implies intro) |
| A ⊢ p -> q |
| E, t1 ⊢ t2 | (t-abs-erased) |
| E ⊢ t1 -> t2 |
| A, p ⊢ q | (implies intro) |
| A ⊢ p -> q |
Assumptions
| (t-var) | |
| E, x : t ⊢ x : t |
| (t-var-erased) | |
| E, t ⊢ t |
| (axiom) | |
| A, p ⊢ p |
Proving the Correspondence
------------------------------------------------- (t-var)
x:X, y:Y ⊢ x : X
------------------------------------------------- (t-abs)
x:X ⊢ fun (y:Y) => x : Y -> X
------------------------------------------------- (t-abs)
⊢ fun (x:X) => (fun (y:Y) => x)) : X -> (Y -> X)
---------------- (axiom)
X, Y ⊢ X
---------------- (implies introduction)
X ⊢ Y -> X
---------------- (implies introduction)
⊢ X -> (Y -> X)
- axiom corresponds to t_var.
- implies_intro corresponds to t_abs.
- implies_elim corresponds to t_app.
- Propositional variables correspond to program variables.
- Implication corresponds to functions and application.
Fixpoint proposition_of_type (t : type) : proposition :=
match t with
| tvar x ⇒ atom x
| arrow t1 t2 ⇒
implies (proposition_of_type t1) (proposition_of_type t2)
end.
Example pot_XY :
proposition_of_type
(arrow (tvar "X") (arrow (tvar "Y") (tvar "X")))
=
(implies (atom "X") (implies (atom "Y") (atom "X"))).
Proof. reflexivity. Qed.
match t with
| tvar x ⇒ atom x
| arrow t1 t2 ⇒
implies (proposition_of_type t1) (proposition_of_type t2)
end.
Example pot_XY :
proposition_of_type
(arrow (tvar "X") (arrow (tvar "Y") (tvar "X")))
=
(implies (atom "X") (implies (atom "Y") (atom "X"))).
Proof. reflexivity. Qed.
With the help of that function, we can convert a type environment into a
list of assumptions:
Definition assumptions_of_tenv (E : tenv) : list proposition :=
map proposition_of_type (map snd E).
Example aot_XY :
assumptions_of_tenv
[("x"%string, tvar "X"); ("y"%string, tvar "Y")]
=
[atom "X"; atom "Y"].
Proof. reflexivity. Qed.
map proposition_of_type (map snd E).
Example aot_XY :
assumptions_of_tenv
[("x"%string, tvar "X"); ("y"%string, tvar "Y")]
=
[atom "X"; atom "Y"].
Proof. reflexivity. Qed.
Exercise: 2 stars, standard (in_snd)
Lemma in_snd : ∀ (X Y Z : Type) (x : X) (y : Y) (f : Y → Z) (l : list (X×Y)),
In (x, y) l → In (f y) (map f (map snd l)).
Proof. (* FILL IN HERE *) Admitted.
☐
In (x, y) l → In (f y) (map f (map snd l)).
Proof. (* FILL IN HERE *) Admitted.
☐
Exercise: 3 stars, standard (curry_howard)
Theorem curry_howard : ∀ E e t,
hastype E e t →
provable (assumptions_of_tenv E) (proposition_of_type t).
Proof. (* FILL IN HERE *) Admitted.
☐
hastype E e t →
provable (assumptions_of_tenv E) (proposition_of_type t).
Proof. (* FILL IN HERE *) Admitted.
☐
Logic: Adding Conjunction and Disjunction
Inductive proposition :=
| atom (id : string)
| implies (p q : proposition)
| and (p q : proposition)
| or (p q : proposition)
| false.
Definition not (f : proposition) : proposition :=
implies f false.
Definition true : proposition :=
implies false false.
Definition iff (p q : proposition) :=
and (implies p q) (implies q p).
| atom (id : string)
| implies (p q : proposition)
| and (p q : proposition)
| or (p q : proposition)
| false.
Definition not (f : proposition) : proposition :=
implies f false.
Definition true : proposition :=
implies false false.
Definition iff (p q : proposition) :=
and (implies p q) (implies q p).
The new proof system rules are as follows:
The new rules can be interpreted as follows:
We formalize those as follows.
| A ⊢ p A ⊢ q | (and intro) |
| A ⊢ p /\ q |
| A ⊢ p /\ q | (and elim L) |
| A ⊢ p |
| A ⊢ p /\ q | (and elim R) |
| A ⊢ q |
| A ⊢ p | (or intro L) |
| A ⊢ p \/ q |
| A ⊢ q | (or intro R) |
| A ⊢ p \/ q |
| A, p ⊢ r A, q ⊢ r A ⊢ p \/ q | (or elim) |
| A ⊢ r |
- And introduction: if it is possible to prove both p and q from
A, then it follows that p ∧ q is provable from A. That is,
a proof of a conjunction consists of a proof of each component.
- And elimination (left): if it is possible to prove p ∧ q from A,
then it follows that p is provable from A. That is, from a proof
of a conjunction, we can extract its first component.
- And elimination (right): if it is possible to prove p ∧ q from A,
then it follows that q is provable from A.
- Or introduction (left): if it is possible to prove p from A, then
it follows that p ∨ q is provable from A. That is, to prove a
disjunction, it suffices to prove one of its components and identify
which component we proved (by choosing the left vs. right rule).
- Or introduction (right): if it is possible to prove q from A, then
it follows that p ∨ q is provable from A.
- Or elimination: if from A, p we can prove r, and from A, q we can prove r, and we also have a proof of p ∨ q from A, then it follows that r is provable from A. That is, to use a proof of a disjunction, we must consider both cases -- assuming p and assuming q -- and show that the same conclusion r follows in each case.
Inductive provable : list proposition → proposition → Prop :=
(* The first four rules were part of our previous development. *)
| axiom : ∀ A p,
In p A →
provable A p
| implies_intro : ∀ A p q,
provable (p :: A) q →
provable A (implies p q)
| implies_elim : ∀ A p q,
provable A (implies p q) →
provable A p →
provable A q
| false_elim : ∀ A p,
provable A false →
provable A p
(* The next six rules are new. *)
| and_intro : ∀ A p q,
provable A p →
provable A q →
provable A (and p q)
| and_elim_L : ∀ A p q,
provable A (and p q) →
provable A p
| and_elim_R : ∀ A p q,
provable A (and p q) →
provable A q
| or_intro_L : ∀ A p q,
provable A p →
provable A (or p q)
| or_intro_R : ∀ A p q,
provable A q →
provable A (or p q)
| or_elim : ∀ A p q r,
provable (p :: A) r →
provable (q :: A) r →
provable A (or p q) →
provable A r.
Ltac by_assumption :=
constructor;
repeat match goal with
| ⊢ In ?p (?h :: ?t) ⇒ simpl
| ⊢ (?x = ?y) ∨ ?p ⇒ first [left; reflexivity | right]
| ⊢ False ⇒ idtac "by_assumption failed: not in assumptions"; fail
end.
(* The first four rules were part of our previous development. *)
| axiom : ∀ A p,
In p A →
provable A p
| implies_intro : ∀ A p q,
provable (p :: A) q →
provable A (implies p q)
| implies_elim : ∀ A p q,
provable A (implies p q) →
provable A p →
provable A q
| false_elim : ∀ A p,
provable A false →
provable A p
(* The next six rules are new. *)
| and_intro : ∀ A p q,
provable A p →
provable A q →
provable A (and p q)
| and_elim_L : ∀ A p q,
provable A (and p q) →
provable A p
| and_elim_R : ∀ A p q,
provable A (and p q) →
provable A q
| or_intro_L : ∀ A p q,
provable A p →
provable A (or p q)
| or_intro_R : ∀ A p q,
provable A q →
provable A (or p q)
| or_elim : ∀ A p q r,
provable (p :: A) r →
provable (q :: A) r →
provable A (or p q) →
provable A r.
Ltac by_assumption :=
constructor;
repeat match goal with
| ⊢ In ?p (?h :: ?t) ⇒ simpl
| ⊢ (?x = ?y) ∨ ?p ⇒ first [left; reflexivity | right]
| ⊢ False ⇒ idtac "by_assumption failed: not in assumptions"; fail
end.
Exercise: 2 stars, standard (false_iff_inconsistency)
Theorem false_iff_inconsistency : ∀ (p : proposition),
provable [] (iff false (and p (not p))).
Proof.
(* FILL IN HERE *) Admitted.
☐
provable [] (iff false (and p (not p))).
Proof.
(* FILL IN HERE *) Admitted.
☐
Theorem and_comm : ∀ (p q : proposition),
provable [] (iff (and p q) (and q p)).
Proof.
(* FILL IN HERE *) Admitted.
☐
provable [] (iff (and p q) (and q p)).
Proof.
(* FILL IN HERE *) Admitted.
☐
Theorem or_comm : ∀ (p q : proposition),
provable [] (iff (or p q) (or q p)).
Proof.
(* FILL IN HERE *) Admitted.
☐
provable [] (iff (or p q) (or q p)).
Proof.
(* FILL IN HERE *) Admitted.
☐
Theorem and_distr_or : ∀ (p q r : proposition),
provable [] (iff (and p (or q r)) (or (and p q) (and p r))).
Proof.
(* FILL IN HERE *) Admitted.
☐
provable [] (iff (and p (or q r)) (or (and p q) (and p r))).
Proof.
(* FILL IN HERE *) Admitted.
☐
Theorem or_distr_and : ∀ (p q r : proposition),
provable [] (iff (or p (and q r)) (and (or p q) (or p r))).
Proof.
(* FILL IN HERE *) Admitted.
☐
provable [] (iff (or p (and q r)) (and (or p q) (or p r))).
Proof.
(* FILL IN HERE *) Admitted.
☐
Programs: Adding Products, Sums, and the Empty Type
Inductive sum (T1 T2 : Type) :=
| inl : T1 → sum T1 T2
| inr : T2 → sum T1 T2.
case e of (x1 : t1) => e1 | (x2 : t2) => e2 end
match e with (x1 : t1) ⇒ e1 | (x2 : t2) ⇒ e2 end
Inductive type :=
| tvar (id : string)
| arrow (t1 t2 : type)
| product (t1 t2 : type)
| sum (t1 t2 : type)
| empty.
Inductive expr :=
| var (id : string)
| abs (id : string) (t : type) (e : expr)
| app (e1 e2 : expr)
| magic (t : type) (e : expr)
| pair (e1 e2 : expr)
| fst (e : expr)
| snd (e : expr)
| inl (e1 : expr)
| inr (e2 : expr)
| case (e : expr) (id1 : string) (t1 : type) (e1 : expr)
(id2 : string) (t2 : type) (e2 : expr).
| tvar (id : string)
| arrow (t1 t2 : type)
| product (t1 t2 : type)
| sum (t1 t2 : type)
| empty.
Inductive expr :=
| var (id : string)
| abs (id : string) (t : type) (e : expr)
| app (e1 e2 : expr)
| magic (t : type) (e : expr)
| pair (e1 e2 : expr)
| fst (e : expr)
| snd (e : expr)
| inl (e1 : expr)
| inr (e2 : expr)
| case (e : expr) (id1 : string) (t1 : type) (e1 : expr)
(id2 : string) (t2 : type) (e2 : expr).
The new type system rules are as follows:
The new rules can be interpreted as follows:
We formalize those as follows.
| E ⊢ e : empty | (t-magic) |
| E ⊢ magic t e : t |
| E ⊢ e1 : t1 E ⊢ e2 : t2 | (t-pair) |
| E ⊢ pair e1 e2 : t1 * t2 |
| E ⊢ e : t1 * t2 | (t-fst) |
| E ⊢ fst e : t1 |
| E ⊢ e : t1 * t2 | (t-snd) |
| E ⊢ snd e : t2 |
| E ⊢ e : t1 | (t-inl) |
| E ⊢ inl e : t1 + t2 |
| E ⊢ e : t2 | (t-inr) |
| E ⊢ inr e : t1 + t2 |
| E ⊢ e : t1 + t2 E, x1:t1 ⊢ e1 : t E, x2:t2 ⊢ e2 : t | (t-case) |
| E ⊢ case e of (x1 : t1) => e1 | (x2 : t2) => e2 end : t |
- T-Magic: if e has type empty, then magic t e has type t for
any t. That is, from an impossible value (one of type empty),
we can produce a value of any type.
- T-Pair: if e1 has type t1 and e2 has type t2, then the pair
pair e1 e2 has type t1 × t2. That is, a pair packages together
two values, one of each type.
- T-Fst: if e has type t1 × t2, then fst e has type t1.
That is, from a pair, we can extract its first component.
- T-Snd: if e has type t1 × t2, then snd e has type t2.
That is, from a pair, we can extract its second component.
- T-Inl: if e has type t1, then inl e has type t1 + t2 for
any type t2. That is, we can inject a value into the left side
of a sum type.
- T-Inr: if e has type t2, then inr e has type t1 + t2 for
any type t1. That is, we can inject a value into the right side
of a sum type.
- T-Case: if e has type t1 + t2, and if assuming x1 : t1 we can show that e1 has type t, and assuming x2 : t2 we can show that e2 has type t, then the case expression has type t. That is, to use a value of sum type, we must consider both possibilities, one for each injection, and ensure that both branches produce the same type of result.
Definition tenv := list (string × type).
Inductive hastype : tenv → expr → type → Prop :=
(* The first three rules were part of our previous development. *)
| t_var : ∀ E x t,
In (x, t) E →
hastype E (var x) t
| t_abs : ∀ E x t1 e t2,
hastype ((x, t1) :: E) e t2 →
hastype E (abs x t1 e) (arrow t1 t2)
| t_app : ∀ E e1 t1 t2 e2,
hastype E e1 (arrow t1 t2) →
hastype E e2 t1 →
hastype E (app e1 e2) t2
(* The next seven rules are new. *)
| t_magic : ∀ E e t,
hastype E e empty →
hastype E (magic t e) t
| t_pair : ∀ E e1 e2 t1 t2,
hastype E e1 t1 →
hastype E e2 t2 →
hastype E (pair e1 e2) (product t1 t2)
| t_fst : ∀ E e t1 t2,
hastype E e (product t1 t2) →
hastype E (fst e) t1
| t_snd : ∀ E e t1 t2,
hastype E e (product t1 t2) →
hastype E (snd e) t2
| t_inl : ∀ E e t1 t2,
hastype E e t1 →
hastype E (inl e) (sum t1 t2)
| t_inr : ∀ E e t1 t2,
hastype E e t2 →
hastype E (inr e) (sum t1 t2)
| t_case : ∀ E e x1 t1 e1 x2 t2 e2 t,
hastype E e (sum t1 t2) →
hastype ((x1, t1) :: E) e1 t →
hastype ((x2, t2) :: E) e2 t →
hastype E (case e x1 t1 e1 x2 t2 e2) t.
Inductive hastype : tenv → expr → type → Prop :=
(* The first three rules were part of our previous development. *)
| t_var : ∀ E x t,
In (x, t) E →
hastype E (var x) t
| t_abs : ∀ E x t1 e t2,
hastype ((x, t1) :: E) e t2 →
hastype E (abs x t1 e) (arrow t1 t2)
| t_app : ∀ E e1 t1 t2 e2,
hastype E e1 (arrow t1 t2) →
hastype E e2 t1 →
hastype E (app e1 e2) t2
(* The next seven rules are new. *)
| t_magic : ∀ E e t,
hastype E e empty →
hastype E (magic t e) t
| t_pair : ∀ E e1 e2 t1 t2,
hastype E e1 t1 →
hastype E e2 t2 →
hastype E (pair e1 e2) (product t1 t2)
| t_fst : ∀ E e t1 t2,
hastype E e (product t1 t2) →
hastype E (fst e) t1
| t_snd : ∀ E e t1 t2,
hastype E e (product t1 t2) →
hastype E (snd e) t2
| t_inl : ∀ E e t1 t2,
hastype E e t1 →
hastype E (inl e) (sum t1 t2)
| t_inr : ∀ E e t1 t2,
hastype E e t2 →
hastype E (inr e) (sum t1 t2)
| t_case : ∀ E e x1 t1 e1 x2 t2 e2 t,
hastype E e (sum t1 t2) →
hastype ((x1, t1) :: E) e1 t →
hastype ((x2, t2) :: E) e2 t →
hastype E (case e x1 t1 e1 x2 t2 e2) t.
Theorem swap_product :
∀ (t1 t2 : type), ∃ (e : expr),
hastype [] e (arrow (product t1 t2) (product t2 t1)).
Proof.
(* FILL IN HERE *) Admitted.
☐
∀ (t1 t2 : type), ∃ (e : expr),
hastype [] e (arrow (product t1 t2) (product t2 t1)).
Proof.
(* FILL IN HERE *) Admitted.
☐
Theorem swap_sum :
∀ (t1 t2 : type), ∃ (e : expr),
hastype [] e (arrow (sum t1 t2) (sum t2 t1)).
Proof.
(* FILL IN HERE *) Admitted.
☐
∀ (t1 t2 : type), ∃ (e : expr),
hastype [] e (arrow (sum t1 t2) (sum t2 t1)).
Proof.
(* FILL IN HERE *) Admitted.
☐
Exercise: 3 stars, standard (product_of_sum__sum_of_product)
Theorem product_of_sum__sum_of_product :
∀ (t1 t2 t3 : type), ∃ (e : expr),
hastype [] e (arrow (product t1 (sum t2 t3))
(sum (product t1 t2) (product t1 t3))).
Proof. (* FILL IN HERE *) Admitted.
☐
∀ (t1 t2 t3 : type), ∃ (e : expr),
hastype [] e (arrow (product t1 (sum t2 t3))
(sum (product t1 t2) (product t1 t3))).
Proof. (* FILL IN HERE *) Admitted.
☐
Proving the Extended Correspondence
- false_elim corresponds to t_magic.
- and_intro corresponds to t_pair.
- and_elim_L corresponds to t_fst.
- and_elim_R corresponds to t_snd.
- or_intro_L corresponds to t_inl.
- or_intro_R corresponds to t_inr.
- or_elim corresponds to t_case.
- (Propositional variables correspond to program variables.)
- (Implication corresponds to functions and application.)
- False corresponds to the empty type.
- Conjunction corresponds to pairs.
- Disjunction corresponds to variants.
Fixpoint proposition_of_type (t : type) : proposition :=
match t with
| tvar x ⇒ atom x
| arrow t1 t2 ⇒
implies (proposition_of_type t1) (proposition_of_type t2)
| product t1 t2 ⇒
and (proposition_of_type t1) (proposition_of_type t2)
| sum t1 t2 ⇒
or (proposition_of_type t1) (proposition_of_type t2)
| empty ⇒ false
end.
Definition assumptions_of_tenv (E : tenv) : list proposition :=
map proposition_of_type (map Datatypes.snd E).
match t with
| tvar x ⇒ atom x
| arrow t1 t2 ⇒
implies (proposition_of_type t1) (proposition_of_type t2)
| product t1 t2 ⇒
and (proposition_of_type t1) (proposition_of_type t2)
| sum t1 t2 ⇒
or (proposition_of_type t1) (proposition_of_type t2)
| empty ⇒ false
end.
Definition assumptions_of_tenv (E : tenv) : list proposition :=
map proposition_of_type (map Datatypes.snd E).
Exercise: 3 stars, standard (curry_howard_extended)
Theorem curry_howard_extended : ∀ E e t,
hastype E e t →
provable (assumptions_of_tenv E) (proposition_of_type t).
Proof. (* FILL IN HERE *) Admitted.
☐
hastype E e t →
provable (assumptions_of_tenv E) (proposition_of_type t).
Proof. (* FILL IN HERE *) Admitted.
☐
The Second Main Theorem of the Correspondence (Advanced)
Fixpoint string_of_proposition (p : proposition) : string :=
match p with
| atom id ⇒ "(" ++ id ++ ")"
| implies p q ⇒
"(implies " ++ (string_of_proposition p) ++ " "
++ (string_of_proposition q) ++ ")"
| and p q ⇒
"(and " ++ (string_of_proposition p) ++ " "
++ (string_of_proposition q) ++ ")"
| or p q ⇒
"(or " ++ (string_of_proposition p) ++ " "
++ (string_of_proposition q) ++ ")"
| false ⇒ "(false)"
end.
match p with
| atom id ⇒ "(" ++ id ++ ")"
| implies p q ⇒
"(implies " ++ (string_of_proposition p) ++ " "
++ (string_of_proposition q) ++ ")"
| and p q ⇒
"(and " ++ (string_of_proposition p) ++ " "
++ (string_of_proposition q) ++ ")"
| or p q ⇒
"(or " ++ (string_of_proposition p) ++ " "
++ (string_of_proposition q) ++ ")"
| false ⇒ "(false)"
end.
Second, we define a function that converts a proposition to
its equivalent type.
Fixpoint type_of_proposition (p : proposition) : type :=
match p with
| atom id ⇒ tvar id
| implies p q ⇒ arrow (type_of_proposition p) (type_of_proposition q)
| and p q ⇒ product (type_of_proposition p) (type_of_proposition q)
| or p q ⇒ sum (type_of_proposition p) (type_of_proposition q)
| false ⇒ empty
end.
match p with
| atom id ⇒ tvar id
| implies p q ⇒ arrow (type_of_proposition p) (type_of_proposition q)
| and p q ⇒ product (type_of_proposition p) (type_of_proposition q)
| or p q ⇒ sum (type_of_proposition p) (type_of_proposition q)
| false ⇒ empty
end.
Third, we define a function to convert assumptions to a type
environment. Each assumption gets a name, which is its conversion
to a string.
Definition tenv_of_assumptions (A : list proposition) : tenv :=
map (fun p ⇒ (string_of_proposition p, type_of_proposition p)) A.
map (fun p ⇒ (string_of_proposition p, type_of_proposition p)) A.
Now we're ready for the second of the main Curry-Howard theorems.
Prove that if a proposition is provable, then there is a well-typed
program whose type corresponds to that proposition.
Hint: proceed by induction on the evidence that p is provable.
In the (many) inductive cases, use the inductive hypotheses to
derive the existence of some subexpressions, then assemble those
into an expression with the required type.
Exercise: 4 stars, advanced (curry_howard_converse)
Theorem curry_howard_converse : ∀ A p,
provable A p →
∃ e, hastype (tenv_of_assumptions A) e (type_of_proposition p).
Proof. (* FILL IN HERE *) Admitted.
☐
provable A p →
∃ e, hastype (tenv_of_assumptions A) e (type_of_proposition p).
Proof. (* FILL IN HERE *) Admitted.
☐
Further Extensions
- Classical axioms, which correspond to control flow operators
(e.g., mechanisms related to exceptions and non-local jumps).
- First-order universal quantification, which corresponds to
dependent types, where types may depend on values.
- First-order existential quantification, which corresponds to
abstract types, which hide implementation details.
- Second-order propositional quantification, which corresponds to polymorphism, where programs can be written generically over types.
(* 2026-08-24 09:55 *)
