How can a coq Set or Type be a proposition
Asked Answered
B

1

12

I'm reading a tutorial on Coq. It constructs a bool type as follows:

Coq < Inductive bool :  Set := true | false.
bool is defined
bool_rect is defined
bool_ind is defined
bool_rec is defined

Then it shows what each of these things are using "Check".

Coq < Check bool_ind.
bool_ind
     : forall P : bool -> Prop, P true -> P false -> forall b : bool, P b

Coq < Check bool_rec.
bool_rec
     : forall P : bool -> Set, P true -> P false -> forall b : bool, P b

Coq < Check bool_rect.
bool_rect
     : forall P : bool -> Type, P true -> P false -> forall b : bool, P b

I understand bool_ind. It says that if something holds for true and it holds for false, then it holds for all b in bool (because those are the only two).

But I don't understand what the expressions for bool_rec or bool_rect mean. It seems as if P true (which is a Set for bool_rec and a Type for bool_rect) is being treated as a propositional value. What am I missing here?

Bazar answered 22/6, 2013 at 18:29 Comment(0)
S
21

Your intuition for bool_ind is spot on, but thinking about why bool_ind means what you said might help clarify the other two. We know that

bool_ind : forall P : bool -> Prop,
             P true ->
             P false ->
             forall b : bool,
               P b

If we read this as a logical formula, we get the same reading you did:

  • For every predicate P on booleans,
    • If P true holds, and
    • If P false holds, then
    • For every boolean b,
      • P b holds.

But this isn't just a logical formula, it's a type. Specifically, it's a (dependent) function type. And as a function type, it says (if you'll allow me the liberty of inventing names for the unnamed arguments and the result):

  • Given a value P : bool -> Prop,
    • A value Pt : P true,
    • A value Pf : P false, and
    • A value b : bool,
      • We can construct a value Pb : P b.

(Of course, this is a curried function, so there are other ways to break down the type into prose, but this is clearest for our purposes.)

The big important thing here, the thing that makes Coq work as a theorem prover while being a programming language (or vice versa) is the Curry-Howard correspondence: types are propositions, and values are proofs of those propositions. For instance, the simple function type -> corresponds to implication, and the dependent function type forall corresponds to universal quantification. (The notation is pretty suggestive :-)) So in Coq, to prove that φ → ψ, we must construct a value of type φ -> ψ: a function that takes a value of type φ (or in other words, a proof of the proposition φ) and uses it to construct a value of type ψ (a proof of the proposition ψ).

In Coq, we can think about all types in this way, whether those types live in Set, Type, or Prop. (So when you say "It seems as if P true (which is a Set for bool rec and a Type for bool_rect) is being treated as a propositional value," you're right!) For instance, let's consider how we'd implement bool_ind ourselves. We'll start by listing all the parameters to the function, along with its return type:

Definition bool_ind' (P  : bool -> Prop)
                     (Pt : P true)
                     (Pf : P false)
                     (b  : bool)
                     : P b :=

So far, so good. At this point, we'd like to return something of type P b, but we don't know what b is. So, as always in these situations, we pattern match:

  match b with

There are now two cases. First, b could be true. In this case, we must want to return something of type P true, and luckily we have such a value: Pt.

    | true  => Pt

The false case is similar:

    | false => Pf
  end.

Note that when we implement bool_ind', it doesn't look very "proofy," but rather very "programmy". Of course, thanks to the Curry-Howard correspondence, these are the same. But note that the very same implementation will suffice for the other two functions:

Definition bool_rec' (P  : bool -> Set)
                     (Pt : P true)
                     (Pf : P false)
                     (b  : bool)
                     : P b :=
  match b with
    | true  => Pt
    | false => Pf
  end.

Definition bool_rect' (P  : bool -> Type)
                      (Pt : P true)
                      (Pf : P false)
                      (b  : bool)
                      : P b :=
  match b with
    | true  => Pt
    | false => Pf
  end.

Looking at this computational definition exposes another way to thing about bool_ind, bool_rec, and bool_rect: they encapsulate what you need to know to talk about every value of bool. But either way, we're packaging up that information: if I know something for true, and something for false, then I know it for all the bools.

The definition of the bool_{ind,rec,rect} functions abstracts over the usual way we write functions on booleans: there's one argument corresponding to the true branch, and one to the false branch. Or, in other words: these functions are just if statements. In a non–dependently-typed language, they could have the simpler type forall S : Set, S -> S -> bool -> S:

Definition bool_simple_rec (S : Set) (St : P) (Sf : P) (b : bool) : S :=
  match b with
    | true  => St
    | false => Sf
  end.

However, because types can depend on values, we must thread the b through the types everywhere. If it turns out we don't want that, though, we can use our more general function and tell :

Definition bool_simple_rec' (S : Set) : S -> S -> bool -> S :=
  bool_rec (fun _ => S).

Nobody ever said our P : bool -> Set had to use the bool!

All of these functions are a lot more interesting for recursive types. For instance, Coq has the following type of natural numbers:

Inductive nat : Set :=  O : nat | S : nat -> nat.

And we have

nat_ind : forall P : nat -> Prop,
            P O ->
            (forall n' : nat, P n' -> P (S n')) ->
            forall n : nat,
              P n

Along with the corresponding nat_rec and nat_rect. (Exercise for the reader: implement these functions directly.)

At first glance, this is just the principle of mathematical induction. However, it's also how we write recursive functions on nats; they're the same thing. In general, recursive functions over nat look like the following:

fix f n => match n with
             | O    => ...
             | S n' => ... f n' ...
           end

The arm of the match following O (the base case) is just the value of type P O. The arm of the match following S n' (the recursive case) is what's passed into the function of type forall n' : nat, P n' -> P (S n'): the n's are the same, and the value of P n' is the result of the recursive call f n'.

Another way to think about the equivalence between the _rec and _ind functions, then—and one which I think is clearer on infinite types than on bool—is that it's the same as the equivalence between mathematical induction (which happens in Prop) and (structural) recursion (which happens in Set and Type).


Let's get praxic and use these functions. We'll define a simple function that converts booleans to natural numbers, and we'll do it both directly and with bool_rec. The simplest way to write this function is with a pattern match:

Definition bool_to_nat_match (b : bool) : nat :=
  match b with
    | true  => 1
    | false => 0
  end.

The alternative definition is

Definition bool_to_nat_rec : bool -> nat :=
  bool_rec (fun _ => nat) 1 0.

And these two functions are the same:

Goal bool_to_nat_match = bool_to_nat_rec.
Proof. reflexivity. Qed.

(Note: these functions are syntactically equal. This is a stronger condition than simply doing the same thing.)

Here, the P : bool -> Set is fun _ => nat; it gives us the return type, which isn't dependent on the argument. Our Pt : P true is 1, the thing to compute when we're given true; similarly, our Pf : P false is 0.

If we want to use the dependency, we have to cook up a useful data type. How about

Inductive has_if (A : Type) : bool -> Type :=
  | has   : A -> has_if A true
  | lacks : has_if A false.

With this definition, has_if A true is isomorphic to A, and has_if A false is isomorphic to unit. We could then have a function which retains its first argument if and only if it's passed true.

Definition keep_if_match' (A : Type) (a : A) (b : bool) : has_if A b :=
  match b with
    | true  => has A a
    | false => lacks A
  end.

The alternative definition is

Definition keep_if_rect (A : Type) (a : A) : forall b : bool, has_if A b :=
  bool_rect (has_if A) (has A a) (lacks A).

And they're again the same:

Goal keep_if_match = keep_if_rect.
Proof. reflexivity. Qed.

Here, the return type of the function is dependent upon the argument b, so our P : bool -> Type actually does something.

Here's a more interesting example, using natural numbers and length-indexed lists. If you haven't seen length-indexed lists, also called vectors, they're exactly what they say on the tin; vec A n is a list of n As.

Inductive vec (A : Type) : nat -> Type :=
  | vnil  : vec A O
  | vcons : forall n, A -> vec A n -> vec A (S n).
Arguments vnil  {A}.
Arguments vcons {A n} _ _.

(The Arguments machinery handles implicit arguments.) Now, we want to produce a list of n copies of some particular element, so we can write that with a fixpoint:

Fixpoint vreplicate_fix {A : Type} (n : nat) (a : A) : vec A n :=
  match n with
    | O    => vnil
    | S n' => vcons a (vreplicate_fix n' a)
  end.

Alternatively, we can use nat_rect:

Definition vreplicate_rect {A : Type} (n : nat) (a : A) : vec A n :=
  nat_rect (vec A) vnil (fun n' v => vcons a v) n.

Note that since nat_rect captures the recursion pattern, vreplicate_rect is not a fixpoint itself. One thing to note is the third argument to nat_rect:

fun n' v => vcons a v

The v there is conceptually the result of the recursive call to vreplicate_rect n' a; nat_rect abstracts out that recursion pattern, so we don't need to call it directly. The n' is indeed the same n' as in vreplicate_fix, but now it seems like we don't need to mention it explicitly. Why is it passed in? That becomes clear if we write out our types:

fun (n' : nat) (v : vec A n') => vcons a v : vec A (S n')

We need n' so we know what type v has, and consequently what type the result has.

Let's see these functions in action:

Eval simpl in vreplicate_fix  0 tt.
Eval simpl in vreplicate_rect 0 tt.
  (* both => = vnil : vec unit 0 *)

Eval simpl in vreplicate_fix  3 true.
Eval simpl in vreplicate_rect 3 true.
  (* both => = vcons true (vcons true (vcons true vnil)) : vec bool 3 *)

And indeed, they're the same:

(* Note: these two functions do the same thing, but are not syntactically
   equal; the former is a fixpoint, the latter is a function which returns a
   fixpoint.  This sort of equality is all you generally need in practice. *)
Goal forall (A : Type) (a : A) (n : nat),
       vreplicate_fix n a = vreplicate_rect n a.
Proof. induction n; [|simpl; rewrite IHn]; reflexivity. Qed.

Above, I posed the exercise of reimplementing nat_rect and friends. Here's the answer:

Fixpoint nat_rect' (P         : nat -> Type)
                   (base_case : P 0)
                   (recurse   : forall n', P n' -> P (S n'))
                   (n         : nat)
                   : P n :=
  match n with
    | O    => base_case
    | S n' => recurse n' (nat_rect' P base_case recurse n')
  end.

This hopefully makes it clear just how nat_rect abstracts the recursion pattern, and why it's sufficiently general.

Scant answered 23/6, 2013 at 0:50 Comment(7)
Thank you for the very detailed answer. I'm still working on understanding it. I didn't know about the Curry-Howard correspondence and I still don't quite get it. How would I use these functions directly (for example to define negation)? (the usage in the tutorial is encapsulated in "elim", so I can't find example where they're used explicitly)Bazar
In your definition of bool_ind, you have: Pt : P true, but isn't P true an element of type Prop. How can something (specifically Pt) have a type whose value is of type Prop? Is Pt's type also Prop? If so, what's the point of P?Bazar
I'm not quite sure what your 1st question is asking. How can you use which functions directly? On your 2nd question: every value in Coq has a type: e.g., O : nat or I : True. But because Coq is dependently typed, types are just (special) values, so they must also have types: nat : Set and True : Prop (the types of types are sometimes called "kinds", esp. in non–dependently-typed languages). And those types must also have types: Set : Type, Prop : Type. So we have Pt : P true : Prop, just as we have O : nat : Set. The point of P is to pick out the particular proposition.Scant
Yes, but I still don't understand what would be an example instance of P true? What is Pt?Bazar
My first question was asking for an example function call to bool_ind. I want to see what all the arguments look like.Bazar
I edited my question to include some examples. Let me know if that doesn't make things clear.Scant
Absolutely. Also I found a better tutorial (actually, it's a textbook) so now I feel like I'm starting to understand this stuff. Would you mind taking a look at my latest question? https://mcmap.net/q/1008151/-how-to-indicate-decreasing-in-size-of-two-coq-inductive-types/403875 Thanks!Bazar

© 2022 - 2024 — McMap. All rights reserved.