Basics: Functional Programming and Reasoning About Programs
Enumerated Types
Days of the Week
Inductive day : Type :=
| monday : day
| tuesday : day
| wednesday : day
| thursday : day
| friday : day
| saturday : day
| sunday : day.
Having defined day, we can write functions that operate on
days.
Definition next_weekday (d:day) : day :=
match d with
| monday => tuesday
| tuesday => wednesday
| wednesday => thursday
| thursday => friday
| friday => monday
| saturday => monday
| sunday => monday
end.
One thing to note is that the argument and return types of
this function are explicitly declared. Like most functional
programming languages, Coq can often work out these types even if
they are not given explicitly -- i.e., it performs some type
inference -- but we'll always include them to make reading
easier.
Having defined a function, we should check that it works on
some examples. There are actually three different ways to do this
in Coq. First, we can use the command Eval simpl to evaluate a
compound expression involving next_weekday. Uncomment the
following and see what they do.
Eval simpl in (next_weekday friday).
Eval simpl in (next_weekday (next_weekday friday)).
Eval simpl in ((fun d => next_weekday (next_weekday d)) friday).
If you have a computer handy, now would be an excellent
moment to fire up the Coq interpreter under your favorite IDE --
either CoqIde or Proof General -- and try this for yourself. Load
this file (Basics.v) from the book's accompanying Coq sources,
find the above example, submit it to Coq, and observe the
result.
The keyword simpl ("simplify") tells Coq precisely how to
evaluate the expression we give it. For the moment, simpl is
the only one we'll need; later on we'll see some alternatives that
are sometimes useful.
Second, we can record what we expect the result to be in
the form of a Coq example:
Example test_next_weekday:
(next_weekday (next_weekday saturday)) = tuesday.
Proof. simpl. reflexivity. Qed.
The details are not important for now (we'll come back to
them in a bit), but essentially this can be read as "The assertion
we've just made can be proved by observing that both sides of the
equality are the same after simplification."
Third, we can ask Coq to "extract," from a Definition, a
program in some other, more conventional, programming
language (OCaml, Scheme, or Haskell) with a high-performance
compiler. This facility is very interesting, since it gives us a
way to construct fully certified programs in mainstream
languages. Indeed, this is one of the main uses for which Coq was
developed. We won't have space to dig further into this topic,
but more information can be found in the Coq'Art book by Bertot
and Castéran, as well as the Coq reference manual.
Although we are rolling our own booleans here for the sake
of building up everything from scratch, Coq does, of course,
provide a default implementation of the booleans in its standard
library, together with a multitude of useful functions and
lemmas. (Take a look at Coq.Init.Datatypes in the Coq library
documentation if you're interested.) Whenever possible, we'll
name our own definitions and theorems so that they exactly
coincide with the ones in the standard library.
Functions over booleans can be defined in the same way as
above:
Definition negb (b:bool) : bool :=
match b with
| true => false
| false => true
end.
Example neg_2_identity : forall (b:bool), negb (negb b) = b.
Proof.
intro b. destruct b; simpl ; reflexivity.
Qed.
Definition andb (b1:bool) (b2:bool) : bool :=
match b1 with
| true => b2
| false => false
end.
Definition orb (b1:bool) (b2:bool) : bool :=
match b1 with
| true => true
| false => b2
end.
The last two illustrate the syntax for multi-argument
function definitions.
The following four "unit tests" constitute a complete
specification -- a truth table -- for the orb function:
Example test_orb1: (orb true false) = true.
Proof. simpl. reflexivity. Qed.
Example test_orb2: (orb false false) = false.
Proof. simpl. reflexivity. Qed.
Example test_orb3: (orb false true ) = true.
Proof. simpl. reflexivity. Qed.
Example test_orb4: (orb true true ) = true.
Proof. simpl. reflexivity. Qed.
A note on notation: We use square brackets to delimit
fragments of Coq code in comments in .v files; this convention,
also used by the coqdoc documentation tool, keeps them visually
separate from the surrounding text. In the html version of the
files, these pieces of text appear in a different font.
The following bit of Coq hackery defines a magic value
called admit that can fill a hole in an incomplete definition or
proof. We'll use it in the definition of nandb in the following
exercise. In general, your job in the exercises is to replace
admit or Admitted with real definitions or proofs.
Exercise: 1 star (nandb)
Complete the definition of the following function, then make sure that the Example assertions below each can be verified by Coq.
Remove "Admitted." and fill in each proof with
"Proof. simpl. reflexivity. Qed."
Example test_nandb1: (nandb true false) = true.
Admitted.
Example test_nandb2: (nandb false false) = true.
Admitted.
Example test_nandb3: (nandb false true) = true.
Admitted.
Example test_nandb4: (nandb true true) = false.
Admitted.
Definition andb3 (b1:bool) (b2:bool) (b3:bool) : bool :=
admit.
Example test_andb31: (andb3 true true true) = true.
Admitted.
Example test_andb32: (andb3 false true true) = false.
Admitted.
Example test_andb33: (andb3 true false true) = false.
Admitted.
Example test_andb34: (andb3 true true false) = false.
Admitted.
admit.
Example test_andb31: (andb3 true true true) = true.
Admitted.
Example test_andb32: (andb3 false true true) = false.
Admitted.
Example test_andb33: (andb3 true false true) = false.
Admitted.
Example test_andb34: (andb3 true true false) = false.
Admitted.
☐
Function Types
Functions like negb itself are also data values, just like
true and false. Their types are called function types, and
they are written with arrows.
The type of negb, written bool -> bool and pronounced
"bool arrow bool," can be read, "Given an input of type
bool, this function produces an output of type bool."
Similarly, the type of andb, written bool -> bool -> bool, can
be read, "Given two inputs, both of type bool, this function
produces an output of type bool."
Numbers
The types we have defined so far are examples of "enumerated
types": their definitions explicitly enumerate a finite set of
elements. A more interesting way of defining a type is to give a
collection of "inductive rules" describing its elements. For
example, we can define the natural numbers as follows:
The clauses of this definition can be read:
Let's look at this in a little more detail.
Every inductively defined set (weekday, nat, bool, etc.) is
actually a set of expressions. The definition of nat says how
expressions in the set nat can be constructed:
These three conditions are the precise force of the
Inductive declaration. They imply that the expression O, the
expression S O, the expression S (S O), the expression
S (S (S O)), and so on all belong to the set nat, while other
expressions like true, andb true false, and S (S false) do
not.
We can write simple functions that pattern match on natural
numbers just as we did above -- for example, predecessor:
- 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.
- the expression O belongs to the set nat;
- if n is an expression belonging to the set nat, then S n is also an expression belonging to the set nat; and
- expressions formed in these two ways are the only ones belonging to the set nat.