In an email, one student in our class, Yunpeng, asked me
how to slove the last three problems. Here, I posted
my version of the solution for your reference. Nevertheless
to say:
1. my solution is by no means the simplest one; and
2. the solution should not be unique, so you're
encouraged to invent your own ones.
Require Export Coq.Init.Peano.
Require Export Coq.Arith.Plus.
Require Export Coq.omega.Omega.
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
(Hint: recall that the definition of nat from class,
Inductive nat : Type :=
| O : nat
| S : nat -> nat.
says nothing about what O and S "mean". It just says "O is a nat (whatever that is), and if n is a nat then so is S n". The interpretation of O as zero and S as successor/plus one comes from the way that we use nat values, by writing functions to do things with them, proving things about them, and so on. Your definition of bin should be correspondingly simple; it is the functions you will write next that will give it mathematical meaning.)
- zero,
- twice a binary number, or
- one more than twice a binary number.
(Hint: recall that the definition of nat from class,
Inductive nat : Type :=
| O : nat
| S : nat -> nat.
says nothing about what O and S "mean". It just says "O is a nat (whatever that is), and if n is a nat then so is S n". The interpretation of O as zero and S as successor/plus one comes from the way that we use nat values, by writing functions to do things with them, proving things about them, and so on. Your definition of bin should be correspondingly simple; it is the functions you will write next that will give it mathematical meaning.)
(b) Next, write an increment function for binary numbers, and a
function to convert binary numbers to unary numbers.
Fixpoint inc(b: bin): bin :=
match b with
| Zero => More Zero
| Twice x => More x
| More x => Twice (inc x)
end.
Here, I define the result type to be nat.
Fixpoint convert (b: bin): nat :=
match b with
| Zero => O
| Twice x => 2 * (convert x)
| More x => 2 * (convert x) + 1
end.
(c) Finally, prove that your increment and binary-to-unary functions commute: that is, incrementing a binary number and then converting it to unary yields the same result as first converting it to unary and then incrementing.
Theorem commu:
forall (b: bin),
convert (inc b) = (convert b) + 1.
Proof.
induction b.
simpl.
reflexivity.
simpl.
reflexivity.
simpl.
rewrite IHb.
omega.
Qed.
This exercise is a continuation of the previous exercise about
binary numbers. You will need your definitions and theorems from
the previous exercise to complete this one.
(a) First, write a function to convert natural numbers to binary numbers. Then prove that starting with any natural number, converting to binary, then converting back yields the same natural number you started with.
(a) First, write a function to convert natural numbers to binary numbers. Then prove that starting with any natural number, converting to binary, then converting back yields the same natural number you started with.
Fixpoint nat2bin (n: nat): bin :=
match n with
| O => Zero
| S n' => inc (nat2bin n')
end.
Theorem binarymap:
forall n: nat,
convert (nat2bin n) = n.
Proof.
induction n.
simpl.
reflexivity.
simpl.
rewrite commu.
rewrite IHn.
omega.
Qed.
(b) You might naturally think that we should also prove the opposite direction: that starting with a binary number, converting to a natural, and then back to binary yields the same number we started with. However, it is not true! Explain what the problem is.
Solution. It's not hard to show that the representation of same
binary is not unique. Consider, for instance, the nat 0
has at least these representations: Zero, Twice Zero,
Twice (Twice Zero), ...
(c) Define a function normalize from binary numbers to binary numbers such that for any binary number b, converting to a natural and then back to binary yields (normalize b). Prove it.
Definition bindouble (b: bin): bin :=
match b with
| Zero => Zero
| Twice x => Twice (Twice x)
| More x => Twice (More x)
end.
Lemma l0:
forall b,
inc (inc (bindouble b)) = bindouble (inc b).
Proof.
induction b.
simpl.
reflexivity.
simpl.
reflexivity.
simpl.
reflexivity.
Qed.
Lemma l1:
forall n,
nat2bin (n + n) = bindouble (nat2bin n).
Proof.
induction n.
simpl.
reflexivity.
simpl.
rewrite <- plus_n_Sm.
simpl.
rewrite IHn.
rewrite l0.
reflexivity.
Qed.
Lemma l2:
forall n,
nat2bin (n + n + 1) = More (nat2bin n).
Proof.
induction n.
simpl.
reflexivity.
simpl.
rewrite <- plus_n_Sm with (m:=n).
rewrite plus_Sn_m.
simpl.
rewrite IHn.
simpl.
reflexivity.
Qed.
Fixpoint normalize (b: bin): bin :=
match b with
| Zero => Zero
| Twice x =>
match normalize x with
| Zero => Zero
| Twice y => Twice (Twice y)
| More y => Twice (More y)
end
| More x => More (normalize x)
end.
Theorem natmap:
forall b: bin,
nat2bin (convert b) = normalize b.
Proof.
induction b.
simpl.
reflexivity.
simpl.
rewrite <- plus_n_O.
rewrite l1.
rewrite IHb.
unfold bindouble.
reflexivity.
simpl.
rewrite <- plus_n_O.
rewrite l2.
rewrite IHb.
reflexivity.
Qed.
The requirement that some argument to each function be
"decreasing" 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.
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 not accept because of this restriction.
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 not accept because of this restriction.
Solution. In Coq's world, as we've seen, the recursive
functions can be defined using Fixpoint. However, for some
reason that we'll discuss later, Coq only allow terminating
functions, that is functions should not perform recursion
infinitely. For instance: this function f
Fixpoint f (n: nat): nat :=
match n with
| O => f (S O)
| S n' => f (S (S n'))
end.
will not accepted by Coq as it will not terminate for any
coming arugments. You may notice that the function even does
NOT compile at all.
Then the one natural problem is: how does Coq judge statically
whether or not
one function will terminate when run. Of course, this
problem is not computable. So Coq adopted a conservative
solution: there is at least one argument decreacing for
recursive call. However, this strategy is too coarse in
some circumstance, for example, this function does
terminate for any input nats, but Coq complains...
Fixpoint f (n: nat): nat :=
match n with
| O => f (S O)
| S n' => O
end.