Some notes for lecture 1


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.

Exercise: 4 stars, recommended (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.
(a) First, write an inductive definition of the type bin corresponding to this description of binary numbers.

(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.)

Inductive bin: Type :=
| Zero: bin
| Twice: bin -> bin
| More: bin -> bin.

(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.

Exercise: 5 stars (binary_inverse)

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.


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.

Exercise: 2 stars, optional (decreasing)

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.

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.