Coq3110An Introduction to Coq for CS 3110



This material is based on an online textbook by Benjamin Pierce et al. titled "Software Foundations":
    http://www.cis.upenn.edu/~bcpierce/sf/ 


Inductive Types

Coq is a functional programming language. We can define data types, just like in OCaml. For example:
  • Days of the week
  • Boolean
  • Natural numbers

Days of the Week

A datatype definition:

Inductive day : Type :=
  | monday : day
  | tuesday : day
  | wednesday : day
  | thursday : day
  | friday : day
  | saturday : day
  | sunday : day.

A function on days:

Definition next_weekday (d:day) : day :=
  match d with
  | mondaytuesday
  | tuesdaywednesday
  | wednesdaythursday
  | thursdayfriday
  | fridaymonday
  | saturdaymonday
  | sundaymonday
  end.

Check checks the type of an expression.

Check next_weekday.
    (* ===> next_weekday : day -> day *)

Computation

Eval compute in e evaluates e and prints the result, much like entering e into the REPL in OCaml.

Eval compute in (next_weekday friday).
   (* ==> monday : day *)
Eval compute in (next_weekday (next_weekday saturday)).
   (* ==> tuesday : day *)

Unit tests

A "unit test" for our function — i.e., a mathematical claim about its behavior:

Example test_next_weekday:
  (next_weekday (next_weekday saturday)) = tuesday.

A proof script giving evidence for the claim:

Proof. reflexivity. Qed.

reflexivity is a tactic that is built into Coq. In essence, it applies Coq's computational rules (i.e., dynamic semantics) to both side of the = sign. Then it compares to see whether both sides reduced to the same value. If so, that counts as evidence that the equality holds.

Extraction into OCaml


Extraction day.
    (* type day =
       | Monday
       ... *)

Extraction next_weekday.
    (* let next_weekday = function
       | Monday -> Tuesday
       ... *)


Booleans

A familiar datatype:

Inductive bool : Type :=
  | true : bool
  | false : bool.

Booleans are also provided in Coq's standard library, but we are defining from scratch, just to see how it's done.

Functions on booleans

We can code these up with using any built-in boolean operators.

Definition negb (b:bool) : bool :=
  match b with
  | truefalse
  | falsetrue
  end.

Coq has an if expression that we could use instead of pattern matching.

Definition andb (b1:bool) (b2:bool) : bool :=
  if b1 then b2 else false.

Definition orb (b1:bool) (b2:bool) : bool :=
  if b1 then true else b2.

Unit tests

These test acheive complete coverage of orb:

Example test_orb1: (orb true false) = true.
Proof. reflexivity. Qed.
Example test_orb2: (orb false false) = false.
Proof. reflexivity. Qed.
Example test_orb3: (orb false true) = true.
Proof. reflexivity. Qed.
Example test_orb4: (orb true true) = true.
Proof. reflexivity. Qed.

Natural Numbers



The natural numbers in unary representation:

Inductive nat : Type :=
  | O : nat
  | S : natnat.

The clauses of this definition can be read:
  • O is a natural number (note that this is the letter "O," not the numeral "0").
  • S is a "constructor" that takes a natural number and yields another one — that is, if n is a natural number, then S n is too.

Two functions on numbers

Add one:

Definition succ (n : nat) : nat :=
  S n.

Subtract one:

(* requires: n >= 1 *)
Definition pred (n : nat) : nat :=
  match n with
    | OO
    | S n'n'
  end.


Syntactic sugar for numbers

Arabic numerals can be used in input as a shorthand for sequences of applications of S to O, and Coq uses the same shorthand on output:

Check (S (S (S (S O)))).
    (* ===> 4 *)
Eval compute in (pred 4).
    (* ===> 3 *)

Recursive functions

Recursive functions are defined using Fixpoint instead of Definition:

Fixpoint evenb (n:nat) : bool :=
  match n with
  | Otrue
  | S Ofalse
  | S (S n') ⇒ evenb n'
  end.

We can define oddb in terms of evenb:

Definition oddb (n:nat) : bool :=
  negb (evenb n).

Example test_oddb1: (oddb 1) = true.
Proof. reflexivity. Qed.
Example test_oddb2: (oddb 4) = false.
Proof. reflexivity. Qed.

Arithmetic



Fixpoint plus (n : nat) (m : nat) : nat :=
  match n with
    | Om
    | S n'S (plus n' m)
  end.

When two arguments have the same type, we can list them together. (n m : nat) means the same as (n : nat) (m : nat).

Fixpoint mult (n m : nat) : nat :=
  match n with
    | OO
    | S n'plus m (mult n' m)
  end.

Fixpoint minus (n m:nat) : nat :=
  match n, m with
  | O , _O
  | S _ , On
  | S n', S m'minus n' m'
  end.


Factorial


Fixpoint factorial (n:nat) : nat :=
  match n with
  | O ⇒ 1
  | S n'mult n (factorial n')
  end.

Example test_factorial1: (factorial 3) = 6.
Proof. reflexivity. Qed.
Example test_factorial2: (factorial 5) = (mult 10 12).
Proof. reflexivity. Qed.

Extensible Syntax


Notation "x + y" := (plus x y)
  (at level 50, left associativity) : nat_scope.
Notation "x - y" := (minus x y)
  (at level 50, left associativity) : nat_scope.
Notation "x × y" := (mult x y)
  (at level 40, left associativity) : nat_scope.

Check ((0 + 1) + 1).

A theorem about addition:


Theorem plus_O_n : ∀ n : nat, 0 + n = n.
Proof. reflexivity. Qed.

Logic

Coq is also a proof assistant. Coq's built-in logic has:
  • Inductive definitions
  • (universal quantification)
  • (implication)
  • that's it!
Everything else ('and', 'or', 'negation', 'equals', ...) can be coded up, and is in the standard library.

Propositions

In Coq, the type of things that can (potentially) be proven is Prop.
Here is an example of a provable proposition:

Check (3 = 3).
(* ===> Prop *)

Here is an example of an unprovable proposition:

Check (∀ (n:nat), n = 2).
(* ===> Prop *)

Values of type Prop

Types are inhabited by values.
  • true and false inhabit bool.
  • 0, 1, ... inhabit nat.
  • what inhabits Prop?
The propositions we try to prove!
  • e.g., 0×3 = 0 inhabits Prop.
But propositions are just another kind of type...
  • what inhabits the type 0×3 = 0?
A proof that provides evidence that 0×3 = 0!

Proofs

Propositions are inhabited by proof objects aka proof terms.

Lemma silly : 0 × 3 = 0.
Proof. reflexivity. Qed.
Print silly.
(* ===> silly = eq_refl : 0 * 3 = 0 *)

(Here, the eq_refl proof object provides evidence for the equality. We'll come back to how it does that...)

Constructing proof objects

We could even have written the proof object ourselves:

Definition silly' : 0 × 3 = 0 :=
  eq_refl.
Print silly'.
Print silly.
(* ===> silly' = eq_refl : 0 * 3 = 0 *)
(* ===> silly = eq_refl : 0 * 3 = 0 *)

These mean the same thing:
  • Lemma name : prop. Proof. tactics Qed.
  • Definition name : prop := proof object .
The first form lets us interactively build a proof object, the latter requires us to state it all at once.

Another Proof Object


Print plus_O_n.
(* ===> plus_O_n = 
          fun n : nat 
            => eq_refl
            : forall n : nat, 0 + n = n *)


This proof object is a function that takes in n and returns a proof object eq_refl.

Reading Coq

We read name = expr : type the same, whether the type is Prop or Type.

Print andb.
(* ===> andb = 
          fun b1 b2 : bool 
            => if b1 then b2 else false
            : bool -> bool -> bool *)

Print plus_O_n.
(* ===> plus_O_n = 
          fun n : nat 
            => eq_refl
            : forall n : nat, 0 + n = n *)


Coq unifies the notions of
  • types with formulas, and
  • programs with proofs.

Coq's Connectives

Same connectives we saw in IQC:
  • Implication
  • Universal quantification
  • Conjunction
  • Disjunction
  • True and False
  • Negation ¬
  • Existential quantification
  • Equality =

Implies and forall

These are the built-in connectives in Coq.
Coq treats both connectives the same way:
  • The proof object for P Q is a function that takes evidence for P as input and produces evidence for Q as output.
  • The proof object for x, P is a function that takes x as input and produces evidence for P as output.

Implies and forall intro

The introduction rules in IQC corresponds to the intros tactic in Coq.
The intros tactic moves one or more quantifiers or hypotheses from the goal to a "context" of current assumptions.

Lemma imp_intro : (1 + 1) = 2 → 0 × 3 = 0.
Proof. intros H. reflexivity. Qed.
Print imp_intro.
(* ===> imp_intro = 
          fun _ : 1 + 1 = 2 => eq_refl
          : 1 + 1 = 2 -> 0 * 3 = 0 *)


The proof object takes in evidence for 1+1=2, discards that evidence, and returns evidence for 0×3=0.


Lemma forall_intro : ∀ (n:nat), n = n.
Proof. intros n. reflexivity. Qed.
Print forall_intro.
(* ===> forall_intro = 
          fun n : nat => eq_refl
          : forall n : nat, n = n *)


The proof object takes in n, and returns evidence for n=n.

Implies and forall elim

The elimination rules for these connectives in IQC correspond to the apply tactic in Coq.

Lemma imp_elim : ∀ (P Q : Prop), P → (PQ) → Q.
Proof.
  intros P Q. intros HP HPimpQ.
  apply HPimpQ. assumption.
Qed.
Print imp_elim.
(* ===> imp_elim = fun (P Q : Prop) (HP : P) 
                       (HPimpQ : P -> Q) 
                   => HPimpQ HP
          : forall P Q : Prop, 
              P -> (P -> Q) -> Q *)


Note:
  • The assumption tactic corresponds to the IQC assumption rule.
  • The proof object applies a function. Implication elimination is really function application!


Lemma forall_elim : (∀ (n:nat), n=0) → 1=0.
Proof. intros H. apply H with (n:=1). Qed.
Print forall_elim.
(* ===> forall_elim = 
          fun H : forall n : nat, n = 0 => H 1
          : (forall n : nat, n = 0) -> 1 = 0 *)


Conjunction

Logical conjunction is not built-in to the language, though it's available in the standard library.
Conjunction is a binary operator on propositions:

Inductive and (P Q : Prop) : Prop :=
  conj : PQ → (and P Q).

Notation "P ∧ Q" := (and P Q) : type_scope.

And intro

And intro in IQC corresponds to applying the conj constructor in Coq.

Theorem and_intro :
  (0 = 0) ∧ (4 = mult 2 2).
Proof.
  apply conj. reflexivity. reflexivity. Qed.

Print and_intro.
(* ===>  and_intro = 
           conj (0 = 0) (4 = 2 * 2) 
             eq_refl eq_refl
           : 0 = 0 /\ 4 = 2 * 2 *)


And elim

And elim in IQC corresponds to the inversion tactic in Coq, which breaks apart the evidence for a Prop into smaller pieces of evidence.

Theorem and_elim : ∀ P Q : Prop,
  PQP.
Proof.
  intros P Q H.
  inversion H as [HP HQ].
  apply HP.
Qed.

A proof we did in IQC


Theorem and_commut : ∀ P Q : Prop,
  PQQP.
Proof.
  intros P Q H.
  inversion H as [HP HQ].
  apply conj.
    apply HQ.
    apply HP.
Qed.

Writing tactics is the "assembly language" programming of Coq. There are automated tactics that find proofs for us!

Theorem and_commut' : ∀ P Q : Prop,
  PQQP.
Proof. firstorder. Qed.

Disjunction


Inductive or (P Q : Prop) : Prop :=
  | or_introl : Por P Q
  | or_intror : Qor P Q.

Notation "P ∨ Q" := (or P Q) : type_scope.

Check or_introl.
(* ===>  forall P Q : Prop, P -> P \/ Q *)

Check or_intror.
(* ===>  forall P Q : Prop, Q -> P \/ Q *)

A proof we did in IQC

Theorem or_commut : ∀ P Q : Prop,
  PQQP.
Proof.
  intros P Q H.
  inversion H as [HP | HQ].
    apply or_intror. apply HP.
    apply or_introl. apply HQ.
Qed.

We could even write down an explicit proof object for or_commut without using tactics to construct it:

Definition or_commut' : ∀ P Q,
  PQQP
:=
    fun (P Q : Prop) (H : PQ) ⇒
      match H with
      | or_introl HPor_intror Q P HP
      | or_intror HQor_introl Q P HQ
      end.

Or we could let Coq find the proof.

Theorem or_commut'' : ∀ P Q : Prop,
  PQQP.
Proof. firstorder. Qed.

True and False

True has a single constructor that takes no arguments.

Inductive True : Prop :=
 I : True.

False has no constructors. Intuition: False is a proposition for which there is no way to give evidence.

Inductive False : Prop := .

Proofs with True and False

Applying the I constructor corresponds to IQC's true intro.

Theorem True_is_provable : True.
Proof. apply I. Qed.

Once we have False as an assumption, the contradiction tactic lets us conclude whatever we want. That corresponds to IQC's false elim.

Theorem False_implies_nonsense :
  False → 2 + 2 = 5.
Proof.
  intros. contradiction.
Qed.

Theorem exfalso : ∀ (P:Prop),
  FalseP.
Proof.
  intros. contradiction.
Qed.

Negation

Negation is defined in Coq exactly as in IQC. The fold and unfold tactics let us convert between ¬P and PFalse.

Definition not (P:Prop) := PFalse.
Notation "¬ x" := (not x) : type_scope.

Theorem not_False : ¬ False.
Proof. unfold not. apply exfalso. Qed.

Theorem contradiction_implies_anything :
  ∀ P Q : Prop, (P ∧ ¬P) → Q.
Proof. firstorder. Qed.

Existential Quantification


Inductive ex (X:Type) (P : XProp) : Prop :=
  ex_intro : ∀ (witness:X), P witnessex X P.

To give evidence we must actually name a witness a specific value x — and then give evidence for P x.


Notation "'exists' x , p" := (ex _ (fun xp))
  (at level 200, x ident, right associativity) : type_scope.
Notation "'exists' x : X , p" := (ex _ (fun x:Xp))
  (at level 200, x ident, right associativity) : type_scope.

Proof with exists

The ex_intro constructor corresponds to IQC's exists intro.

Example exists_example_1 :
  ∃ n, n + (n × n) = 6.
Proof.
  apply ex_intro with (witness:=2).
  reflexivity.
Qed.

Note that we explicitly give the witness.
We'll omit exists elimination, though it also is in Coq.

A proof we did in IQC


Theorem dist_exists_or : ∀ (X:Type) (P Q : XProp),
  (∃ x, P xQ x) ↔ (∃ x, P x) ∨ (∃ x, Q x).
Proof.
  firstorder.
Qed.

Equality


The equality relation is definable, not built-in to the language.

Inductive eq {X:Type} : XXProp :=
  refl_equal : ∀ x, eq x x.

Notation "x = y" := (eq x y)
  (at level 70, no associativity) : type_scope.

Lemma four: 2 + 2 = 1 + 3.
Proof.
  apply refl_equal.
Qed.

reflexivity is essentially just apply refl_equal.


Excluded middle

Note that some theorems that are true in classical logic are not provable in Coq's (constructive) logic. E.g., let's look at how this proof gets stuck...

Theorem excluded_middle : ∀ P : Prop,
  P ∨ ¬P.
Proof.
  intros P. unfold not.
  (* We either have to go left or right... *)
  left. (* But now what?  We don't have evidence for P. *)
  Undo.
  right. intros HP. (* And now we don't have evidence for False. *)
Abort.

Proofs from IPC and IQC

Here are Coq proofs for all the theorems we proved in IQC and IPC. The firstorder tactic searches for, and finds, a proof object for each of these theorems. You can enter Print <theorem_name> to see that object.

Theorem lec19thm1 : ∀ (A B : Prop),
  A → (BA).
Proof. firstorder. Qed.

Theorem lec19thm2 : ∀ (A B : Prop),
  A → (B → (AB)).
Proof. firstorder. Qed.

Theorem lec19thm3 : ∀ (A B : Prop),
  (AB) → (BA).
Proof. firstorder. Qed.

Theorem rec18thm1 : ∀ (A B : Prop),
  (AB) → A.
Proof. firstorder. Qed.

Theorem rec18thm2 : ∀ (A B : Prop),
  (AB) → (BA).
Proof. firstorder. Qed.

Theorem rec18thm3 : ∀ (A B C : Prop),
  (AB) → ((BC) → (AC)).
Proof. firstorder. Qed.

Theorem rec18thm4 : ∀ (S C O : Prop),
  ((SC) ∧ O ∧ ((O->~C) ∧ (C->~O))) → ¬S.
Proof. firstorder. Qed.

Theorem lec21thm1 : ∀ (Q R : TypeProp),
  (∀ x, R(x) ∧ Q(x)) → (∀ x, R(x)) ∧ (∀ x, Q(x)).
Proof. firstorder. Qed.

Theorem lec21thm2 : ∀ (Q R : TypeProp),
  (∃ x, Q(x) ∨ R(x)) → (∃ x, R(x)) ∨ (∃ x, Q(x)).
Proof. firstorder. Qed.

Theorem rec19thm1 : ∀ (A B : Prop),
  ((AB) ∧ ¬B) → ¬A.
Proof. firstorder. Qed.

Theorem rec19thm2 : ∀ (A B : Prop),
  (A → (B ∨ (AB))) → (AB).
Proof. firstorder. Qed.

Theorem rec19thm3 : ∀ (A B : Prop),
  ~((~A ∨ ¬B) ∧ (AB)).
Proof. firstorder. Qed.

Theorem rec19thm4 : ∀ (P Q : TypeProp),
  (∀ (x y : Type), (P(x) → Q(y))) ∧ (∃ x, P(x))
    → (∃ y, Q(y)).
Proof. firstorder. Qed.

Theorem rec19thm5 : ∀ (P Q : TypeProp),
  (∀ x, P(x)) → (∀ x, Q(x)) → (∀ x, (P(x) ↔ Q(x))).
Proof. firstorder. Qed.