Episode 02: Hello PeaCoq
(* (* they can even be nested *) *)
Coq code is primarily a sequence of bindings. In PeaCoq, we can step through the bindings one at a time by pressing:
Coq also has commands which let us poke around and ask Coq questions. For example, the Print command displays the value associated with a name:
Control-Alt-DownWhen we ask Coq to process a binding like:
Definition foo := bar.it does the following:
- Type check the expression bar.
- This ensures that, if asked, Coq will be able to evaluate bar down to a value.
- Extend the environment to associate the name foo to the expression bar.
Print x.
It only works on names though; this will crash:
The Check command displays the type of an expression:
Print 1.
The Eval cbv in command evaluates an expression down to a value:
Here is a simple inductive definition:
It says that boolean is a type and that there are exactly two values that have type boolean, btrue and bfalse.
Coq actually doesn't have any built-in types! However, the standard prelude provides a several familiar types. For example, the type bool from the standard prelude is just like our type boolean:
Print bool.
We can also bind names to functions by including arguments to our Definitions:
This binds a function bid to a function which takes a sinlge argument, x of type bool and when called simply returns x.
We call functions via juxtaposition which is a fancy way of saying we just write the function name and the argument next to it:
In Coq we can also prove that our functions work as expected. Here is a simple proof showing that bid has the behavior we expect: it always just returns its argument:
The tactic intro above transforms the proof goal from:
You can think of it as proving a universally quantified claim about booleans by considering an arbitrary boolean.
============================ forall b : bool, bid b = bto:
b : bool ============================ bid b = b
The tactic unfold bid above transforms the proof goal to:
This simply replaces the identifier bid with whatever it is bound to in the current environment.
b : bool ============================ b = b
reflexivity.
Finally, the reflexivity tactic solves this subgoal. Since we don't have any outstanding subgoals, the proof is done and we end with a nice Qed.
Qed.
We can also define functions that do case analysis, or pattern matching, on their arguments:
Definition bneg (b: bool) : bool :=
match b with
| true => false
| false => true
end.
Eval cbv in (bneg true).
Eval cbv in (bneg false).
To define functions of multiple arguments, just add another argument!
Definition band (l: bool) (r: bool) : bool :=
match l with
| true =>
match r with
| true => true
| false => false
end
| false => false
end.
Eval cbv in (band true true).
Eval cbv in (band true false).
Eval cbv in (band false true).
Eval cbv in (band false false).
We can also lump arguments of the same type together.
Definition bor (l r: bool) : bool :=
match l with
| true => true
| false =>
match r with
| true => true
| false => false
end
end.
Eval cbv in (bor true true).
Eval cbv in (bor true false).
Eval cbv in (bor false true).
Eval cbv in (bor false false).
What types do our functions have?
bid : bool -> bool
bid : bool -> bool
band : bool -> bool -> bool
bor : bool -> bool -> bool
intros works like intro, but it slurps up all the universally quantified variables at the front of a goal.
intros.
destruct does case analysis.
destruct l.
In the case that l = true, we want to also do case analysis on r:
destruct r.
We can solve both the subcases with reflexivity.
reflexivity.
reflexivity.
reflexivity.
In the case that l = false, reflexivity does the trick.
{ reflexivity. }
Qed.
Qed.
Another of DeMorgan's laws can be proved similarly:
Lemma demorgan_bor:
forall l r,
bor l r = bneg (band (bneg l) (bneg r)).
Proof.
intros. destruct l.
{ reflexivity. }
{ destruct r.
{ reflexivity. }
{ reflexivity. }
}
Qed.
The curly baces ``{'' and ``}'' help us note which subgoals we're in and make the structure of the proof a little bit clearer. While they're technically optional, PeaCoq will always generate them to help make the resulting proof term easier to understand.
Side Note : We can write our functions to just bind a name to an expression and use fun to build an anonymous function:
Definition bneg' : bool -> bool :=
fun (b: bool) =>
match b with
| true => false
| false => true
end.
Also we can often omit certain type annotations, and Coq will figure them out automatically!
Definition bneg'' :=
fun (b: bool) =>
match b with
| true => false
| false => true
end.
Check bneg''.
Definition bneg''' : bool -> bool :=
fun b =>
match b with
| true => false
| false => true
end.
Check bneg'''.
The same goes for functions with multiple arguments:
Definition band' : bool -> bool -> bool :=
fun (l: bool) =>
fun (r: bool) =>
match l with
| true =>
match r with
| true => true
| false => false
end
| false => false
end.
Lemma band_equiv_band':
forall b, band b = band' b.
Proof.
reflexivity.
Qed.
OK, enough booleans. Let's look at numbers.
Definition nid (x: nat) := x.
Eval cbv in (nid 0).
Eval cbv in (nid 1).
Eval cbv in (nid 2).
Check (nid 2).
Lemma nid_ok :
forall n, nid n = n.
Proof.
reflexivity.
Qed.
nat is defined a bit differently from bool:
Print nat.
Inductive nat : Set := | O : nat | S : nat -> nat.
Eval cbv in O.
Eval cbv in (S O).
Eval cbv in (S (S O)).
Eval cbv in (S (S (S O))).
Eval cbv in (S (S (S (S O)))).
This also means that if we want to write functions over nat, we need recursive definitions, which we introduce with Fixpoint:
Fixpoint nadd (n1 n2: nat) : nat :=
match n1 with
| O => n2
| S n => S (nadd n n2)
end.
Fixpoint nmult (n1 n2: nat) : nat :=
match n1 with
| O => O
| 1 => n2
| S n => nadd n2 (nmult n n2)
end.
Fixpoint factorial n :=
match n with
| O => 1
| S m => n * factorial m
end.
We can prove things about functions over nat similar to our proofs about functions over bool:
However, some of these proof will also require induction, which we'll talk about extensively during the course:
Lemma nadd_n_0:
forall n, nadd n 0 = n.
Proof.
intro. induction n.
{ reflexivity. }
{ simpl. rewrite -> IHn. reflexivity. }
Qed.
Question : Why did we need induction for nadd_n_0 but not for nadd_0_n ?
Consider these types for representing pairs of nats and booleans and also booleans and nats respectively:
Inductive nat_and_bool :=
| nnb : nat -> bool -> nat_and_bool.
Inductive bool_and_nat :=
| bnn : bool -> nat -> bool_and_nat.
We can write functions that swap these around:
Definition swap_nat_and_bool (x : nat_and_bool) : bool_and_nat :=
match x with
| nnb n b => bnn b n
end.
Definition swap_bool_and_nat x :=
match x with
| bnn b n => nnb n b
end.
We can also prove that these swaps work as we expect:
Lemma swap_swap:
forall x, swap_nat_and_bool (swap_bool_and_nat x) = x.
Proof.
intro. destruct x0. reflexivity.
Qed.
These definitions are overly specific though, we should define some sort of generic pair type. Unfortunately, our technique for writing parameterized definitions doesn't really cut it here:
Instead we can use the type prod:
Definition pair x y := ... (* what do we put here ?! *)
Print prod.
Inductive prod A B := | pair : A -> B -> A * B
Note that we don't have to include all the type arguments. This is due to Coq's inference mechanisms, which we will disucuss in more detail later.
We can also prove our generic swap correct:
Lemma swap_ok:
forall A B (p: A * B),
swap B A (swap A B p) = p.
Proof.
intros. destruct p. reflexivity.
Qed.
One parameterized type named option is super useful for encoding partial functions:
Print option.
Inductive option (A : Type) : Type := | Some : A -> option A | None : option A.
Next time we'll talk more about inductive types and how they can be used to encode syntax. However, you should check out the list type and think about it until then:
Print list.
Inductive list (A : Type) : Type := | nil : list A | cons : A -> list A -> list A.
This page has been generated by coqdoc