Recursion
Module NatPlayground2.
Fixpoint plus (n : nat) (m : nat) : nat :=
match n with
| O ⇒ m
| S n' ⇒ S (plus n' m)
end.
Fixpoint plus (n : nat) (m : nat) : nat :=
match n with
| O ⇒ m
| S n' ⇒ S (plus n' m)
end.
Adding three to two gives us five, as we'd expect.
The simplification that Coq performs to reach this
conclusion can be visualized as follows, a little more succinctly
than what we wrote for evenb last time:
(* plus (S (S (S O))) (S (S O))
==> S (plus (S (S O)) (S (S O)))
by the second clause of the match
==> S (S (plus (S O) (S (S O))))
by the second clause of the match
==> S (S (S (plus O (S (S O)))))
by the second clause of the match
==> S (S (S (S (S O))))
by the first clause of the match
*)
==> S (plus (S (S O)) (S (S O)))
by the second clause of the match
==> S (S (plus (S O) (S (S O))))
by the second clause of the match
==> S (S (S (plus O (S (S O)))))
by the second clause of the match
==> S (S (S (S (S O))))
by the first clause of the match
*)
As a notational convenience, if two or more arguments have
the same type, they can be written together. In the following
definition, (n m : nat) means just the same as if we had written
(n : nat) (m : nat).
Fixpoint mult (n m : nat) : nat :=
match n with
| O ⇒ O
| S n' ⇒ plus m (mult n' m)
end.
Compute (mult 3 3).
Compute (mult 5 2).
match n with
| O ⇒ O
| S n' ⇒ plus m (mult n' m)
end.
Compute (mult 3 3).
Compute (mult 5 2).
You can match two expressions at once by putting a comma
between them:
Fixpoint minus (n m:nat) : nat :=
match n, m with
| O , _ ⇒ O
| S _ , O ⇒ n
| S n', S m' ⇒ minus n' m'
end.
match n, m with
| O , _ ⇒ O
| S _ , O ⇒ n
| S n', S m' ⇒ minus n' m'
end.
Our minus function has the same problem pred does: minus 0 n
results in 0! This 'truncating' subtraction is necessary because
we're working with natural numbers, i.e., there are no negative
numbers.
Keep in mind: these definitions have suggestive names, but it's up
to us as humans to agree that minus corresponds to our notion of
subtraction. I would wager that it does: if we pick an n and an
m such that m ≤ n, then minus n m yields n - m. We
don't have the tools to prove this... yet!
Again, the _ in the first line is a wildcard pattern. Writing
_ in a pattern is the same as writing some variable that doesn't
get used on the right-hand side. This avoids the need to invent a
variable name.
The pow function defines exponentiation: pow n m should yield
n to the mth power.
Exercise: 1 star, standard (factorial)
Here's the standard mathematical factorial function:factorial(0) = 1 factorial(n) = n * factorial(n-1) (if n>0)
With a little work, we can use the arithmetic-like syntax we're
accustomed to:
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).
(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).
(The level, associativity, and nat_scope annotations
control how these notations are treated by Coq's parser. The
details are not important for our purposes, but interested readers
can refer to the optional "More on Notation" section at the end of
this chapter.)
Note that these do not change the definitions we've already made:
they are simply instructions to the Coq parser to accept e.g. x +
y in place of plus x y and, conversely, to the Coq
pretty-printer to display plus x y as x + y.
When Coq checks this definition, it notes that plus' is
"decreasing on 1st argument." What this means is that we are
performing a structural recursion over the argument n -- i.e.,
that we make recursive calls only on strictly smaller values of
n. This implies that all calls to plus' will eventually
terminate. Coq demands that some argument of every Fixpoint
definition is "decreasing."
This requirement is a fundamental feature of Coq's design: In
particular, it guarantees that every function that can be defined
in Coq will terminate on all inputs. However, because Coq's
"decreasing analysis" is not very sophisticated, it is sometimes
necessary to write functions in slightly unnatural ways.
You can write it Fail Fixpoint ... to ensure it fails to compile!
Exercise: 2 stars, standard (decreasing)
To get a concrete sense of this, find a way to write a sensible Fixpoint definition (of a simple function on numbers, say) that does terminate on all inputs, but that Coq will reject because of this restriction.
(* FILL IN HERE *)
(* Do not modify the following line: *)
Definition manual_grade_for_decreasing : option (nat×string) := None.
☐
(* Do not modify the following line: *)
Definition manual_grade_for_decreasing : option (nat×string) := None.
☐
Recall our definition of pred, the predecessor function on
nats.
Definition pred (n : nat) : nat :=
match n with
| O ⇒ O
| S n' ⇒ n'
end.
Check pred. (* ===> nat -> nat *)
Check (pred 5). (* ===> nat *)
Compute (pred 5). (* ===> 4 *)
Compute (pred 0). (* ===> 0 (nobody's perfect) *)
match n with
| O ⇒ O
| S n' ⇒ n'
end.
Check pred. (* ===> nat -> nat *)
Check (pred 5). (* ===> nat *)
Compute (pred 5). (* ===> 4 *)
Compute (pred 0). (* ===> 0 (nobody's perfect) *)
In addition to the Check command to see an expression's type and
the Compute command to see how it evaluates, there's a Print
command to see something's definition. Let's try it.
Huh, weird! We defined pred with the Definition command, but
Coq used a fun. It turns out that these notions are
equivalent.
That is, writing:
Definition foo (x:T) (y:U) : Z := blah.
is just the same as:
Definition foo : T → U → Z := fun (x:T) (y:U) : Z ⇒ blah.
which is just the same as:
Definition foo (x:T) : U → Z := fun (y:U) ⇒ blah.
It's very useful to know equivalences in your programming
language!
It's also important to know the various forms of syntax. A
Definition takes the form:
Definition NAME ARGS× : RETURN TYPE := EXPRESSION.
where NAME is an identifier and ARGS× zero or more arguments
(optionally, wrapped in parentheses and annotated with
types). (It's conventional in computer science to use * to mean
"zero or more". The name comes from the 20th Century theorist
Stephen Cole Kleene.) The syntax for anonymous functions is
similar:
fun ARGS+ ⇒ EXPRESSION
Where the + means one or more.
For Definitions return types are optional and for funs they
are forbidden; a Definition need not have arguments, but
anonymous functions need them.
Finally---we introduced some of these concepts without such a nice
description yesterday. Hopefully things are clearer now!
What about recursive functions? I briefly showed you
evenb'... let's look at the recursive double function again,
in a bit more detail:
Definition foo (x:T) (y:U) : Z := blah.
Definition foo : T → U → Z := fun (x:T) (y:U) : Z ⇒ blah.
Definition foo (x:T) : U → Z := fun (y:U) ⇒ blah.
Definition NAME ARGS× : RETURN TYPE := EXPRESSION.
fun ARGS+ ⇒ EXPRESSION
Fixpoint double (n:nat) :=
match n with
| O ⇒ O
| S n' ⇒ S (S (double n'))
end.
Print double.
(* ===>
double = fix double (n : nat) : nat := match n with
| 0 => 0
| S n' => S (S (double n'))
end
: nat -> nat
*)
match n with
| O ⇒ O
| S n' ⇒ S (S (double n'))
end.
Print double.
(* ===>
double = fix double (n : nat) : nat := match n with
| 0 => 0
| S n' => S (S (double n'))
end
: nat -> nat
*)
Rather than writing fun, it says fix double. Interesting!
After that, it looks mostly like a fun, though there's a return
type, too.
Here's another equivalence, where ARGS+ means one or more arguments:
Fixpoint NAME ARGS+ : RETURN TYPE := EXPRESSION.
is just the same as:
Definition NAME := fix NAME ARGS+ : RETURN TYPE := EXPRESSION.
Fixpoint NAME ARGS+ : RETURN TYPE := EXPRESSION.
Definition NAME := fix NAME ARGS+ : RETURN TYPE := EXPRESSION.
Exercise: 2 stars, standard (half)
Define a function half : nat → nat that divides a number by two, rounding down.
Fixpoint half (n:nat) : nat (* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Compute (half 4). (* ==> 2 *)
Compute (half 9). (* ==> 4 *)
☐
Compute (half 4). (* ==> 2 *)
Compute (half 9). (* ==> 4 *)
☐
Exercise: 2 stars, standard (third)
Define a function third : nat → nat that divides a number by three, rounding down.
Fixpoint third (n:nat) : nat (* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Compute (third 4). (* ==> 1 *)
Compute (third 9). (* ==> 3 *)
☐
Compute (third 4). (* ==> 1 *)
Compute (third 9). (* ==> 3 *)
☐
Case study: binary and trinary numbers
Exercise: 3 stars, standard (binary)
Consider a different, more efficient representation of natural numbers using a binary rather than unary system. That is, instead of saying that each natural number is either zero or the successor of a natural number, we can say that each binary number is either- zero,
- twice a binary number, or
- one more than twice a binary number.
Inductive bin : Type :=
| BZ (* zero *)
| T2 (n : bin) (* twice n *)
| T2P1 (n : bin). (* twice n, plus one *)
| BZ (* zero *)
| T2 (n : bin) (* twice n *)
| T2P1 (n : bin). (* twice n, plus one *)
How does this work? Let's compare some binary and unary
numbers. Zero is easy:
One isn't so bad either. To get 1 in unary, just add an S. To
get it in binary, well, what could we do? If we simply enumerate
possibilities, the next up after BZ itself are T2 BZ and
T2P1.
T2 BZ is "twice BZ", i.e., "twice zero", i.e. "zero". Not what
we want.
T2P1 BZ is "twice BZ plus one", i.e., "zero plus one", i.e.,
"one". Perfect!
For the number two, the nats do the same thing: just add an S.
Noting that 2 = 2 * 1, we have our answer for binary, too.
Let's do three.
Let's skip a few. What about the number nine?
Definition u_nine : nat := S (S (S (S (S (S (S (S (S O)))))))).
Definition b_nine : bin := T2P1 (T2 (T2 (T2P1 BZ))).
Definition b_nine : bin := T2P1 (T2 (T2 (T2P1 BZ))).
Eleven?
Definition u_eleven : nat := S (S (S (S (S (S (S (S (S (S (S O)))))))))).
Definition b_eleven : bin := T2P1 (T2P1 (T2 (T2P1 BZ))).
Definition b_eleven : bin := T2P1 (T2P1 (T2 (T2P1 BZ))).
You may be wondering... what do T2 and T2P1 and BZ have to
do with, you know, binary? 0s and 1s?
To see, take a binary number and working from BZ out, write 1
for T2P1 and then 0 for T2.
These are indeed the binary numbers!
(a) First, write an increment function incr for binary numbers,
and a function bin_to_nat to convert binary numbers to unary
numbers.
(b) Write five unit tests for your increment and binary-to-unary
functions. (A "unit test" in Coq is just a Compute that you can
quickly check.) Notice that incrementing a binary number and then
converting it to unary should yield the same result as first
converting it to unary and then incrementing. You only need to
write five tests total, but please be certain to test both
functions.
- b_one: 1
- b_two: 10
- b_three: 11
- b_nine: 1001
- b_eleven: 1011
(* FILL IN HERE *)
(* Do not modify the following line: *)
Definition manual_grade_for_binary : option (nat×string) := None.
☐
(* Do not modify the following line: *)
Definition manual_grade_for_binary : option (nat×string) := None.
☐
Exercise: 2 stars, standard (nat_to_bin)
(* FILL IN HERE *)
(* Do not modify the following line: *)
Definition manual_grade_for_nat_to_bin : option (nat×string) := None.
☐
(* Do not modify the following line: *)
Definition manual_grade_for_nat_to_bin : option (nat×string) := None.
☐
(* Answer to (b): 0 has many representations: it can be written
BZ, T2 BZ, T2 T2 BZ, and so on.
For these alternate representations if you do bin_to_nat
then nat_to_bin, you don't get back what you started with.
(Any other number also has many representations, after applying
constructors to the multiple representations of zero.) *)
BZ, T2 BZ, T2 T2 BZ, and so on.
For these alternate representations if you do bin_to_nat
then nat_to_bin, you don't get back what you started with.
(Any other number also has many representations, after applying
constructors to the multiple representations of zero.) *)
(* FILL IN HERE *)
Fixpoint normalize (b:bin) : bin (* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
(* Do not modify the following line: *)
Definition manual_grade_for_normalize : option (nat×string) := None.
☐
Fixpoint normalize (b:bin) : bin (* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
(* Do not modify the following line: *)
Definition manual_grade_for_normalize : option (nat×string) := None.
☐
What's happening here?
(* 2022-01-12 10:20 *)