Logic: Logic in Coqpart 2
Existential Quantification
Definition Even x := ∃ n : nat, x = double n.
Check Even : nat → Prop.
Lemma four_is_Even : Even 4.
Proof.
unfold Even. ∃ 2. reflexivity.
Qed.
Check Even : nat → Prop.
Lemma four_is_Even : Even 4.
Proof.
unfold Even. ∃ 2. reflexivity.
Qed.
Conversely, if we have an existential hypothesis ∃ x, P in
the context, we can destruct it to obtain a witness x and a
hypothesis stating that P holds of x.
Theorem exists_example_2 : ∀ n,
(∃ m, n = 4 + m) →
(∃ o, n = 2 + o).
Proof.
(* WORKED IN CLASS *)
intros n [m Hm]. (* note implicit destruct here *)
∃ (2 + m).
apply Hm. Qed.
(∃ m, n = 4 + m) →
(∃ o, n = 2 + o).
Proof.
(* WORKED IN CLASS *)
intros n [m Hm]. (* note implicit destruct here *)
∃ (2 + m).
apply Hm. Qed.
Exercise: 1 star, standard, especially useful (dist_not_exists)
Prove that "P holds for all x" implies "there is no x for which P does not hold." (Hint: destruct H as [x E] works on existential assumptions!)
Theorem dist_not_exists : ∀ (X:Type) (P : X → Prop),
(∀ x, P x) → ¬ (∃ x, ¬ P x).
Proof.
(* FILL IN HERE *) Admitted.
☐
(∀ x, P x) → ¬ (∃ x, ¬ P x).
Proof.
(* FILL IN HERE *) Admitted.
☐
Exercise: 2 stars, standard (dist_exists_or)
Prove that existential quantification distributes over disjunction.
Theorem dist_exists_or : ∀ (X:Type) (P Q : X → Prop),
(∃ x, P x ∨ Q x) ↔ (∃ x, P x) ∨ (∃ x, Q x).
Proof.
(* FILL IN HERE *) Admitted.
☐
(∃ x, P x ∨ Q x) ↔ (∃ x, P x) ∨ (∃ x, Q x).
Proof.
(* FILL IN HERE *) Admitted.
☐
Theorem leb_plus_exists : ∀ n m, n <=? m = true → ∃ x, m = n+x.
Proof.
(* FILL IN HERE *) Admitted.
Theorem plus_exists_leb : ∀ n m, (∃ x, m = n+x) → n <=? m = true.
Proof.
(* FILL IN HERE *) Admitted.
☐
Proof.
(* FILL IN HERE *) Admitted.
Theorem plus_exists_leb : ∀ n m, (∃ x, m = n+x) → n <=? m = true.
Proof.
(* FILL IN HERE *) Admitted.
☐
Programming with Propositions
- If l is the empty list, then x cannot occur in it, so the property "x appears in l" is simply false.
- Otherwise, l has the form x' :: l'. In this case, x occurs in l if it is equal to x' or if it occurs in l'.
Fixpoint In {A : Type} (x : A) (l : list A) : Prop :=
match l with
| [] ⇒ False
| x' :: l' ⇒ x' = x ∨ In x l'
end.
match l with
| [] ⇒ False
| x' :: l' ⇒ x' = x ∨ In x l'
end.
When In is applied to a concrete list, it expands into a
concrete sequence of nested disjunctions.
Example In_example_1 : In 4 [1; 2; 3; 4; 5].
Proof.
(* WORKED IN CLASS *)
simpl. right. right. right. left. reflexivity.
Qed.
Example In_example_2 :
∀ n, In n [2; 4] →
∃ n', n = 2 × n'.
Proof.
(* WORKED IN CLASS *)
simpl.
intros n [H | [H | []]].
- ∃ 1. rewrite <- H. reflexivity.
- ∃ 2. rewrite <- H. reflexivity.
Qed.
Proof.
(* WORKED IN CLASS *)
simpl. right. right. right. left. reflexivity.
Qed.
Example In_example_2 :
∀ n, In n [2; 4] →
∃ n', n = 2 × n'.
Proof.
(* WORKED IN CLASS *)
simpl.
intros n [H | [H | []]].
- ∃ 1. rewrite <- H. reflexivity.
- ∃ 2. rewrite <- H. reflexivity.
Qed.
(Notice the use of the empty pattern to discharge the last case
en passant.)
We can also prove more generic, higher-level lemmas about In.
(Note how In starts out applied to a variable and only
gets expanded when we do case analysis on this variable.)
Theorem In_map :
∀ (A B : Type) (f : A → B) (l : list A) (x : A),
In x l →
In (f x) (map f l).
Proof.
intros A B f l x.
induction l as [|x' l' IHl'].
- (* l = nil, contradiction *)
simpl. intros [].
- (* l = x' :: l' *)
simpl. intros [H | H].
+ rewrite H. left. reflexivity.
+ right. apply IHl'. apply H.
Qed.
∀ (A B : Type) (f : A → B) (l : list A) (x : A),
In x l →
In (f x) (map f l).
Proof.
intros A B f l x.
induction l as [|x' l' IHl'].
- (* l = nil, contradiction *)
simpl. intros [].
- (* l = x' :: l' *)
simpl. intros [H | H].
+ rewrite H. left. reflexivity.
+ right. apply IHl'. apply H.
Qed.
This way of defining propositions recursively, though convenient
in some cases, also has some drawbacks. In particular, it is
subject to Coq's usual restrictions regarding the definition of
recursive functions, e.g., the requirement that they be "obviously
terminating." In the next chapter, we will see how to define
propositions inductively -- a different technique with its own set
of strengths and limitations.
Exercise: 2 stars, standard (In_map_iff)
Theorem In_map_exists :
∀ (A B : Type) (f : A → B) (l : list A) (y : B),
In y (map f l) →
∃ x, f x = y ∧ In x l.
Proof.
(* FILL IN HERE *) Admitted.
Theorem In_map_iff :
∀ (A B : Type) (f : A → B) (l : list A) (y : B),
In y (map f l) ↔
∃ x, f x = y ∧ In x l.
Proof.
(* FILL IN HERE *) Admitted.
☐
∀ (A B : Type) (f : A → B) (l : list A) (y : B),
In y (map f l) →
∃ x, f x = y ∧ In x l.
Proof.
(* FILL IN HERE *) Admitted.
Theorem In_map_iff :
∀ (A B : Type) (f : A → B) (l : list A) (y : B),
In y (map f l) ↔
∃ x, f x = y ∧ In x l.
Proof.
(* FILL IN HERE *) Admitted.
☐
Theorem In_app_iff : ∀ A l l' (a:A),
In a (l++l') ↔ In a l ∨ In a l'.
Proof.
intros A l. induction l as [|a' l' IH].
(* FILL IN HERE *) Admitted.
☐
In a (l++l') ↔ In a l ∨ In a l'.
Proof.
intros A l. induction l as [|a' l' IH].
(* FILL IN HERE *) Admitted.
☐
Exercise: 3 stars, standard, especially useful (All)
Recall that functions returning propositions can be seen as properties of their arguments. For instance, if P has type nat → Prop, then P n states that property P holds of n.
Fixpoint All {T : Type} (P : T → Prop) (l : list T) : Prop
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Example test_All : ∀ (P : nat → Prop), All P [1; 2; 3] = (P 1 ∧ P 2 ∧ P 3 ∧ True).
Proof.
(* FILL IN HERE *) Admitted.
Theorem All_In' :
∀ T (P : T → Prop) (l : list T),
All P l →
∀ x, In x l → P x.
Proof.
(* FILL IN HERE *) Admitted.
Theorem All_In :
∀ T (P : T → Prop) (l : list T),
(∀ x, In x l → P x) ↔
All P l.
Proof.
(* FILL IN HERE *) Admitted.
☐
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Example test_All : ∀ (P : nat → Prop), All P [1; 2; 3] = (P 1 ∧ P 2 ∧ P 3 ∧ True).
Proof.
(* FILL IN HERE *) Admitted.
Theorem All_In' :
∀ T (P : T → Prop) (l : list T),
All P l →
∀ x, In x l → P x.
Proof.
(* FILL IN HERE *) Admitted.
Theorem All_In :
∀ T (P : T → Prop) (l : list T),
(∀ x, In x l → P x) ↔
All P l.
Proof.
(* FILL IN HERE *) Admitted.
☐
Exercise: 2 stars, standard, optional (combine_odd_even)
Complete the definition of the combine_odd_even function below. It takes as arguments two properties of numbers, Podd and Peven, and it should return a property P such that P n is equivalent to Podd n when n is odd and equivalent to Peven n otherwise.
Definition combine_odd_even (Podd Peven : nat → Prop) : nat → Prop
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
To test your definition, prove the following facts:
Theorem combine_odd_even_intro :
∀ (Podd Peven : nat → Prop) (n : nat),
(odd n = true → Podd n) →
(odd n = false → Peven n) →
combine_odd_even Podd Peven n.
Proof.
(* FILL IN HERE *) Admitted.
Theorem combine_odd_even_elim_odd :
∀ (Podd Peven : nat → Prop) (n : nat),
combine_odd_even Podd Peven n →
odd n = true →
Podd n.
Proof.
(* FILL IN HERE *) Admitted.
Theorem combine_odd_even_elim_even :
∀ (Podd Peven : nat → Prop) (n : nat),
combine_odd_even Podd Peven n →
odd n = false →
Peven n.
Proof.
(* FILL IN HERE *) Admitted.
☐
∀ (Podd Peven : nat → Prop) (n : nat),
(odd n = true → Podd n) →
(odd n = false → Peven n) →
combine_odd_even Podd Peven n.
Proof.
(* FILL IN HERE *) Admitted.
Theorem combine_odd_even_elim_odd :
∀ (Podd Peven : nat → Prop) (n : nat),
combine_odd_even Podd Peven n →
odd n = true →
Podd n.
Proof.
(* FILL IN HERE *) Admitted.
Theorem combine_odd_even_elim_even :
∀ (Podd Peven : nat → Prop) (n : nat),
combine_odd_even Podd Peven n →
odd n = false →
Peven n.
Proof.
(* FILL IN HERE *) Admitted.
☐
Applying Theorems to Arguments
Check plus : nat → nat → nat.
Check add_comm : ∀ n m : nat, n + m = m + n.
Check plus_id_example : ∀ n m : nat, n = m → n + n = m + m.
Check add_comm : ∀ n m : nat, n + m = m + n.
Check plus_id_example : ∀ n m : nat, n = m → n + n = m + m.
Coq checks the type of the statements of the add_comm and
plus_id_example theorems in the same way that it checks the
type of any term (e.g., plus) that we ask it to Check. And if
we leave off the colon and type, Coq will print these types for us.
Why?
The reason is that the identifier add_comm actually refers to a
proof term, which represents a logical derivation establishing
the truth of the statement ∀ n m : nat, n + m = m + n. The
type of this term is the proposition that it is a proof of.
The type of an ordinary function tells us what we can do with it.
Similarly, the statement of a theorem tells us what we can use
that theorem for.
Operationally, this analogy goes even further: by applying a
theorem as if it were a function, i.e., applying it to values and
hypotheses with matching types, we can specialize its result
without having to resort to intermediate assertions. For example,
suppose we wanted to prove the following result:
- e.g., if we have a term of type nat → nat → nat, we can give it two nats as arguments and get a nat back.
- if we have a term of type ∀ n m, n = m → n + n = m + m and we provide it two numbers n and m plus an "argument" of type n = m, we can derive n + n = m + m.
It appears at first sight that we ought to be able to prove this
by rewriting with add_comm twice to make the two sides match.
The problem, however, is that the second rewrite will undo the
effect of the first.
Proof.
intros x y z.
rewrite add_comm.
rewrite add_comm.
(* We are back where we started... *)
Abort.
intros x y z.
rewrite add_comm.
rewrite add_comm.
(* We are back where we started... *)
Abort.
We saw similar problems back in Chapter Induction, and we saw one
way to work around them by using assert to derive a specialized
version of add_comm that can be used to rewrite exactly where
we want.
Lemma add_comm3_take2 :
∀ x y z, x + (y + z) = (z + y) + x.
Proof.
intros x y z.
rewrite add_comm.
assert (H : y + z = z + y).
{ rewrite add_comm. reflexivity. }
rewrite H.
reflexivity.
Qed.
∀ x y z, x + (y + z) = (z + y) + x.
Proof.
intros x y z.
rewrite add_comm.
assert (H : y + z = z + y).
{ rewrite add_comm. reflexivity. }
rewrite H.
reflexivity.
Qed.
A more elegant alternative is to apply add_comm directly
to the arguments we want to instantiate it with, in much the same
way as we apply a polymorphic function to a type argument.
Lemma add_comm3_take3 :
∀ x y z, x + (y + z) = (z + y) + x.
Proof.
intros x y z.
rewrite add_comm.
rewrite (add_comm y z).
reflexivity.
Qed.
∀ x y z, x + (y + z) = (z + y) + x.
Proof.
intros x y z.
rewrite add_comm.
rewrite (add_comm y z).
reflexivity.
Qed.
We can in fact do it for both uses of rewrite
Lemma add_comm3_take4 :
∀ x y z, x + (y + z) = (z + y) + x.
Proof.
intros x y z.
rewrite (add_comm x (y + z)).
rewrite (add_comm y z).
reflexivity.
Qed.
∀ x y z, x + (y + z) = (z + y) + x.
Proof.
intros x y z.
rewrite (add_comm x (y + z)).
rewrite (add_comm y z).
reflexivity.
Qed.
Let's see another example of using a theorem like a function.
The following theorem says: any list l containing some element
must be nonempty.
What makes this interesting is that one quantified variable
(x) does not appear in the conclusion (l ≠ []).
We should be able to use this theorem to prove the special case
where x is 42. However, naively, the tactic apply in_not_nil
will fail because it cannot infer the value of x.
Lemma in_not_nil_42 :
∀ l : list nat, In 42 l → l ≠ [].
Proof.
intros l H.
Fail apply in_not_nil.
Abort.
∀ l : list nat, In 42 l → l ≠ [].
Proof.
intros l H.
Fail apply in_not_nil.
Abort.
There are several ways to work around this:
Use apply ... with ...
Lemma in_not_nil_42_take2 :
∀ l : list nat, In 42 l → l ≠ [].
Proof.
intros l H.
apply in_not_nil with (x := 42).
apply H.
Qed.
∀ l : list nat, In 42 l → l ≠ [].
Proof.
intros l H.
apply in_not_nil with (x := 42).
apply H.
Qed.
Use apply ... in ...
Lemma in_not_nil_42_take3 :
∀ l : list nat, In 42 l → l ≠ [].
Proof.
intros l H.
apply in_not_nil in H.
apply H.
Qed.
∀ l : list nat, In 42 l → l ≠ [].
Proof.
intros l H.
apply in_not_nil in H.
apply H.
Qed.
Explicitly apply the lemma to the value for x.
Lemma in_not_nil_42_take4 :
∀ l : list nat, In 42 l → l ≠ [].
Proof.
intros l H.
apply (in_not_nil nat 42).
apply H.
Qed.
∀ l : list nat, In 42 l → l ≠ [].
Proof.
intros l H.
apply (in_not_nil nat 42).
apply H.
Qed.
Explicitly apply the lemma to a hypothesis.
Lemma in_not_nil_42_take5 :
∀ l : list nat, In 42 l → l ≠ [].
Proof.
intros l H.
apply (in_not_nil _ _ _ H).
Qed.
∀ l : list nat, In 42 l → l ≠ [].
Proof.
intros l H.
apply (in_not_nil _ _ _ H).
Qed.
You can "use a theorem as a function" in this way with almost any
tactic that can take a theorem's name as an argument.
Note, also, that theorem application uses the same inference
mechanisms as function application; thus, it is possible, for
example, to supply wildcards as arguments to be inferred, or to
declare some arguments to a theorem as implicit by default.
These features are illustrated in the proofs below.
Example lemma_application_ex1 :
∀ {n : nat} {ns : list nat},
In n (map (fun m ⇒ m × 0) ns) →
n = 0.
Proof.
intros n ns H.
destruct (In_map_exists _ _ _ _ _ H) as [m [Hm _]].
rewrite (mul_0_r m) in Hm. rewrite <- Hm. reflexivity.
Qed.
Example lemma_application_ex2 :
∀ (n : nat) (ns : list nat),
In n (map (fun m ⇒ m × 0) ns) →
n × n = 0.
Proof.
intros n ns H. rewrite (lemma_application_ex1 H). reflexivity.
Qed.
∀ {n : nat} {ns : list nat},
In n (map (fun m ⇒ m × 0) ns) →
n = 0.
Proof.
intros n ns H.
destruct (In_map_exists _ _ _ _ _ H) as [m [Hm _]].
rewrite (mul_0_r m) in Hm. rewrite <- Hm. reflexivity.
Qed.
Example lemma_application_ex2 :
∀ (n : nat) (ns : list nat),
In n (map (fun m ⇒ m × 0) ns) →
n × n = 0.
Proof.
intros n ns H. rewrite (lemma_application_ex1 H). reflexivity.
Qed.
We will see many more examples in later chapters.
Coq vs. Set Theory
Functional Extensionality
This works when Coq can simplify the functions to the same expression,
but this doesn't always work.
In common mathematical practice, two functions f and g are
considered equal if they produce the same output on every input:
(∀ x, f x = g x) → f = g This is known as the principle of functional extensionality.
Informally, an "extensional property" is one that pertains to an
object's observable behavior. Thus, functional extensionality
simply means that a function's identity is completely determined
by what we can observe from it -- i.e., the results we obtain
after applying it.
However, functional extensionality is not part of Coq's built-in
logic. This means that some apparently "obvious" propositions are
not provable.
(∀ x, f x = g x) → f = g This is known as the principle of functional extensionality.
Example function_equality_ex2 :
(fun x ⇒ plus x 1) = (fun x ⇒ plus 1 x).
Proof.
Fail reflexivity. Fail rewrite add_comm.
(* Stuck *)
Abort.
(fun x ⇒ plus x 1) = (fun x ⇒ plus 1 x).
Proof.
Fail reflexivity. Fail rewrite add_comm.
(* Stuck *)
Abort.
However, if we like, we can add functional extensionality to Coq
using the Axiom command.
Defining something as an Axiom has the same effect as stating a
theorem and skipping its proof using Admitted, but it alerts the
reader that this isn't just something we're going to come back and
fill in later!
We can now invoke functional extensionality in proofs:
Example function_equality_ex2 :
(fun x ⇒ plus x 1) = (fun x ⇒ plus 1 x).
Proof.
apply functional_extensionality. intros x.
apply add_comm.
Qed.
(fun x ⇒ plus x 1) = (fun x ⇒ plus 1 x).
Proof.
apply functional_extensionality. intros x.
apply add_comm.
Qed.
Naturally, we must be careful when adding new axioms into Coq's
logic, as this can render it inconsistent -- that is, it may
become possible to prove every proposition, including False,
2+2=5, etc.!
Unfortunately, there is no simple way of telling whether an axiom
is safe to add: hard work by highly trained mathematicians is
often required to establish the consistency of any particular
combination of axioms.
Fortunately, it is known that adding functional extensionality, in
particular, is consistent.
To check whether a particular proof relies on any additional
axioms, use the Print Assumptions command:
Print Assumptions function_equality_ex2.
(* ===>
Axioms:
functional_extensionality :
forall (X Y : Type) (f g : X -> Y),
(forall x : X, f x = g x) -> f = g *)
Axioms:
functional_extensionality :
forall (X Y : Type) (f g : X -> Y),
(forall x : X, f x = g x) -> f = g *)
(If you try this yourself, you may also see add_comm listed as
an assumption, depending on whether the copy of Tactics.v in the
local directory has the proof of add_comm filled in.)
Exercise: 4 stars, standard (tr_rev_correct)
One problem with the definition of the list-reversing function rev that we have is that it performs a call to app on each step; running app takes time asymptotically linear in the size of the list, which means that rev is asymptotically quadratic. We can improve this with the following definitions:
Fixpoint rev_append {X} (l1 l2 : list X) : list X :=
match l1 with
| [] ⇒ l2
| x :: l1' ⇒ rev_append l1' (x :: l2)
end.
Definition tr_rev {X} (l : list X) : list X :=
rev_append l [].
match l1 with
| [] ⇒ l2
| x :: l1' ⇒ rev_append l1' (x :: l2)
end.
Definition tr_rev {X} (l : list X) : list X :=
rev_append l [].
This version of rev is said to be tail-recursive, because the
recursive call to the function is the last operation that needs to
be performed (i.e., we don't have to execute ++ after the
recursive call); a decent compiler will generate very efficient
code in this case.
Prove that the two definitions are indeed equivalent.
Propositions vs. Booleans
We've seen two different ways of expressing logical claims in Coq: with booleans (of type bool), and with propositions (of type Prop).bool Prop ==== ==== decidable? yes no useable with match? yes no equalities rewritable? no yes
Working with Decidable Properties
... or that there exists some k such that n = double k.
Of course, it would be pretty strange if these two
characterizations of evenness did not describe the same set of
natural numbers! Fortunately, we can prove that they do...
We first need two helper lemmas.
Lemma even_double : ∀ k, even (double k) = true.
Proof.
intros k. induction k as [|k' IHk'].
- reflexivity.
- simpl. apply IHk'.
Qed.
intros k. induction k as [|k' IHk'].
- reflexivity.
- simpl. apply IHk'.
Qed.
Lemma even_double_conv : ∀ n, ∃ k,
n = if even n then double k else S (double k).
n = if even n then double k else S (double k).
Proof.
(* Hint: Use the even_S lemma from Induction.v. *)
(* FILL IN HERE *) Admitted.
☐
(* Hint: Use the even_S lemma from Induction.v. *)
(* FILL IN HERE *) Admitted.
☐
Now the main theorem:
Theorem even_bool_prop : ∀ n,
even n = true ↔ Even n.
even n = true ↔ Even n.
Proof.
intros n. split.
- intros H. destruct (even_double_conv n) as [k Hk].
rewrite Hk. rewrite H. ∃ k. reflexivity.
- intros [k Hk]. rewrite Hk. apply even_double.
Qed.
intros n. split.
- intros H. destruct (even_double_conv n) as [k Hk].
rewrite Hk. rewrite H. ∃ k. reflexivity.
- intros [k Hk]. rewrite Hk. apply even_double.
Qed.
In view of this theorem, we say that the boolean computation
even n is reflected in the truth of the proposition
∃ k, n = double k.
Similarly, to state that two numbers n and m are equal, we can
say either
- (1) that n =? m returns true, or
- (2) that n = m.
Even when the boolean and propositional formulations of a claim
are equivalent from a purely logical perspective, they are often
not equivalent from the point of view of suitability for some
specific purpose.
In the case of even numbers above, when proving the
backwards direction of even_bool_prop (i.e., even_double,
going from the propositional to the boolean claim), we used a
simple induction on k. On the other hand, the converse (the
even_double_conv exercise) required a clever generalization,
since we can't directly prove (even n = true) → Even n.
Similarly, we cannot test whether or not a Prop is
true in a function definition; as a consequence, the following
code fragment is rejected:
Coq complains that n = 2 has type Prop, while it expects
an element of bool (or some other inductive type with two
constructors). The reason has to do with the computational nature
of Coq's core language, which is designed so that every function
it can express is computable and total. One reason for this is to
allow the extraction of executable programs from Coq developments.
As a consequence, Prop in Coq does not have a universal case
analysis operation telling whether any given proposition is true
or false, since such an operation would allow us to write
non-computable functions.
Beyond the fact that non-computable properties are impossible in
general to phrase as boolean computations, even many computable
properties are easier to express using Prop than bool, since
recursive function definitions in Coq are subject to significant
restrictions. For instance, the next chapter shows how to define
the property that a regular expression matches a given string
using Prop. Doing the same with bool would amount to writing
a regular expression matching algorithm, which would be more
complicated, harder to understand, and harder to reason about than
a simple (non-algorithmic) definition of this property.
Conversely, an important side benefit of stating facts using
booleans is enabling some proof automation through computation
with Coq terms, a technique known as proof by reflection.
Consider the following statement:
The most direct way to prove this is to give the value of k
explicitly.
The proof of the corresponding boolean statement is even
simpler, because we don't have to invent the witness: Coq's
computation mechanism does it for us!
What is interesting is that, since the two notions are equivalent,
we can use the boolean formulation to prove the other one without
mentioning the value 500 explicitly:
Although we haven't gained much in terms of proof-script
size in this case, larger proofs can often be made considerably
simpler by the use of reflection. As an extreme example, a famous
Coq proof of the even more famous 4-color theorem uses
reflection to reduce the analysis of hundreds of different cases
to a boolean computation.
Another notable difference is that the negation of a "boolean
fact" is straightforward to state and prove: simply flip the
expected boolean result.
In contrast, propositional negation can be more difficult
to work with directly.
Example not_even_1001' : ~(Even 1001).
Proof.
(* WORKED IN CLASS *)
rewrite <- even_bool_prop.
unfold not.
simpl.
intro H.
discriminate H.
Qed.
Proof.
(* WORKED IN CLASS *)
rewrite <- even_bool_prop.
unfold not.
simpl.
intro H.
discriminate H.
Qed.
Equality provides a complementary example, where it is sometimes
easier to work in the propositional world.
Knowing that (n =? m) = true is generally of little direct help in
the middle of a proof involving n and m; however, if we
convert the statement to the equivalent form n = m, we can
rewrite with it.
Lemma plus_eqb_example : ∀ n m p : nat,
n =? m = true → n + p =? m + p = true.
Proof.
(* WORKED IN CLASS *)
intros n m p H.
rewrite eqb_eq in H.
rewrite H.
rewrite eqb_eq.
reflexivity.
Qed.
n =? m = true → n + p =? m + p = true.
Proof.
(* WORKED IN CLASS *)
intros n m p H.
rewrite eqb_eq in H.
rewrite H.
rewrite eqb_eq.
reflexivity.
Qed.
We won't discuss reflection any further for the moment, but
it serves as a good example showing the different strengths of
booleans and general propositions. Being able to cross back and forth between the boolean and
propositional worlds will often be convenient in later chapters.
Exercise: 2 stars, standard (logical_connectives)
The following theorems relate the propositional connectives studied in this chapter to the corresponding boolean operations.
Theorem andb_true_iff : ∀ b1 b2:bool,
b1 && b2 = true ↔ b1 = true ∧ b2 = true.
Proof.
(* FILL IN HERE *) Admitted.
Theorem orb_true_iff : ∀ b1 b2,
b1 || b2 = true ↔ b1 = true ∨ b2 = true.
Proof.
(* FILL IN HERE *) Admitted.
☐
b1 && b2 = true ↔ b1 = true ∧ b2 = true.
Proof.
(* FILL IN HERE *) Admitted.
Theorem orb_true_iff : ∀ b1 b2,
b1 || b2 = true ↔ b1 = true ∨ b2 = true.
Proof.
(* FILL IN HERE *) Admitted.
☐
Exercise: 1 star, standard (eqb_neq)
The following theorem is an alternate "negative" formulation of eqb_eq that is more convenient in certain situations. (We'll see examples in later chapters.) Hint: not_true_iff_false.Exercise: 3 stars, standard (eqb_list)
Given a boolean operator eqb for testing equality of elements of some type A, we can define a function eqb_list for testing equality of lists with elements in A. Complete the definition of the eqb_list function below. To make sure that your definition is correct, prove the lemma eqb_list_true_iff.
Fixpoint eqb_list {A : Type} (eqb : A → A → bool)
(l1 l2 : list A) : bool
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Theorem eqb_list_true_iff :
∀ A (eqb : A → A → bool),
(∀ a1 a2, eqb a1 a2 = true ↔ a1 = a2) →
∀ l1 l2, eqb_list eqb l1 l2 = true ↔ l1 = l2.
Proof.
(* FILL IN HERE *) Admitted.
☐
(l1 l2 : list A) : bool
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Theorem eqb_list_true_iff :
∀ A (eqb : A → A → bool),
(∀ a1 a2, eqb a1 a2 = true ↔ a1 = a2) →
∀ l1 l2, eqb_list eqb l1 l2 = true ↔ l1 = l2.
Proof.
(* FILL IN HERE *) Admitted.
☐
Exercise: 2 stars, standard, especially useful (All_forallb)
Prove the theorem below, which relates forallb, from the exercise forall_exists_challenge in chapter Tactics, to the All property defined above.
Fixpoint forallb {X : Type} (test : X → bool) (l : list X) : bool
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Theorem forallb_true_iff : ∀ X test (l : list X),
forallb test l = true ↔ All (fun x ⇒ test x = true) l.
Proof.
(* FILL IN HERE *) Admitted.
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Theorem forallb_true_iff : ∀ X test (l : list X),
forallb test l = true ↔ All (fun x ⇒ test x = true) l.
Proof.
(* FILL IN HERE *) Admitted.
(Ungraded thought question) Are there any important properties of
the function forallb which are not captured by this
specification?
(* FILL IN HERE *)
☐
☐
Classical vs. Constructive Logic
To understand operationally why this is the case, recall
that, to prove a statement of the form P ∨ Q, we use the left
and right tactics, which effectively require knowing which side
of the disjunction holds. But the universally quantified P in
excluded_middle is an arbitrary proposition, which we know
nothing about. We don't have enough information to choose which
of left or right to apply, just as Coq doesn't have enough
information to mechanically decide whether P holds or not inside
a function.
However, if we happen to know that P is reflected in some
boolean term b, then knowing whether it holds or not is trivial:
we just have to check the value of b.
Theorem restricted_excluded_middle : ∀ P b,
(P ↔ b = true) → P ∨ ¬ P.
Proof.
intros P [] H.
- left. rewrite H. reflexivity.
- right. rewrite H. intros contra. discriminate contra.
Qed.
(P ↔ b = true) → P ∨ ¬ P.
Proof.
intros P [] H.
- left. rewrite H. reflexivity.
- right. rewrite H. intros contra. discriminate contra.
Qed.
In particular, the excluded middle is valid for equations n = m,
between natural numbers n and m.
Theorem restricted_excluded_middle_eq : ∀ (n m : nat),
n = m ∨ n ≠ m.
Proof.
intros n m.
apply (restricted_excluded_middle (n = m) (n =? m)).
symmetry.
apply eqb_eq.
Qed.
n = m ∨ n ≠ m.
Proof.
intros n m.
apply (restricted_excluded_middle (n = m) (n =? m)).
symmetry.
apply eqb_eq.
Qed.
It may seem strange that the general excluded middle is not
available by default in Coq, since it is a standard feature of
familiar logics like ZFC. But there is a distinct advantage in
not assuming the excluded middle: statements in Coq make stronger
claims than the analogous statements in standard mathematics.
Notably, when there is a Coq proof of ∃ x, P x, it is
always possible to explicitly exhibit a value of x for which we
can prove P x -- in other words, every proof of existence is
constructive.
Logics like Coq's, which do not assume the excluded middle, are
referred to as constructive logics.
More conventional logical systems such as ZFC, in which the
excluded middle does hold for arbitrary propositions, are referred
to as classical.
The following example illustrates why assuming the excluded middle
may lead to non-constructive proofs:
Claim: There exist irrational numbers a and b such that
a ^ b (a to the power b) is rational.
Proof: It is not difficult to show that sqrt 2 is irrational.
If sqrt 2 ^ sqrt 2 is rational, it suffices to take a = b =
sqrt 2 and we are done. Otherwise, sqrt 2 ^ sqrt 2 is
irrational. In this case, we can take a = sqrt 2 ^ sqrt 2 and
b = sqrt 2, since a ^ b = sqrt 2 ^ (sqrt 2 × sqrt 2) = sqrt 2 ^
2 = 2. ☐
Do you see what happened here? We used the excluded middle to
consider separately the cases where sqrt 2 ^ sqrt 2 is rational
and where it is not, without knowing which one actually holds!
Because of that, we finish the proof knowing that such a and b
exist but we cannot determine what their actual values are (at least,
not from this line of argument).
As useful as constructive logic is, it does have its limitations:
There are many statements that can easily be proven in classical
logic but that have only much more complicated constructive proofs, and
there are some that are known to have no constructive proof at
all! Fortunately, like functional extensionality, the excluded
middle is known to be compatible with Coq's logic, allowing us to
add it safely as an axiom. However, we will not need to do so
here: the results that we cover can be developed entirely
within constructive logic at negligible extra cost.
It takes some practice to understand which proof techniques must
be avoided in constructive reasoning, but arguments by
contradiction, in particular, are infamous for leading to
non-constructive proofs. Here's a typical example: suppose that
we want to show that there exists x with some property P,
i.e., such that P x. We start by assuming that our conclusion
is false; that is, ¬ ∃ x, P x. From this premise, it is not
hard to derive ∀ x, ¬ P x. If we manage to show that this
intermediate fact results in a contradiction, we arrive at an
existence proof without ever exhibiting a value of x for which
P x holds!
The technical flaw here, from a constructive standpoint, is that
we claimed to prove ∃ x, P x using a proof of
¬ ¬ (∃ x, P x). Allowing ourselves to remove double
negations from arbitrary statements is equivalent to assuming the
excluded middle, as shown in one of the exercises below. Thus,
this line of reasoning cannot be encoded in Coq without assuming
additional axioms.
Succinctly: for any proposition P,
Coq is consistent ==> (Coq + P ∨ ¬P) is consistent.
Exercise: 3 stars, standard (excluded_middle_irrefutable)
Proving the consistency of Coq with the general excluded middle axiom requires complicated reasoning that cannot be carried out within Coq itself. However, the following theorem implies that it is always safe to assume a decidability axiom (i.e., an instance of excluded middle) for any particular Prop P. Why? Because the negation of such an axiom leads to a contradiction. If ¬ (P ∨ ¬P) were provable, then by de_morgan_not_or as proved above, P ∧ ¬P would be provable, which would be a contradiction. So, it is safe to add P ∨ ¬P as an axiom for any particular P.
Theorem excluded_middle_irrefutable: ∀ (P : Prop),
¬ ¬ (P ∨ ¬ P).
Proof.
(* FILL IN HERE *) Admitted.
☐
¬ ¬ (P ∨ ¬ P).
Proof.
(* FILL IN HERE *) Admitted.
☐
Exercise: 3 stars, advanced (not_exists_dist)
It is a theorem of classical logic that the following two assertions are equivalent:¬(∃ x, ¬P x)
∀ x, P x The dist_not_exists theorem above proves one side of this equivalence. Interestingly, the other direction cannot be proved in constructive logic. Your job is to show that it is implied by the excluded middle.
Theorem not_exists_dist :
excluded_middle →
∀ (X:Type) (P : X → Prop),
¬ (∃ x, ¬ P x) → (∀ x, P x).
Proof.
(* FILL IN HERE *) Admitted.
☐
excluded_middle →
∀ (X:Type) (P : X → Prop),
¬ (∃ x, ¬ P x) → (∀ x, P x).
Proof.
(* FILL IN HERE *) Admitted.
☐
Exercise: 5 stars, standard, optional (classical_axioms)
For those who like a challenge, here is an exercise taken from the Coq'Art book by Bertot and Casteran (p. 123). Each of the following four statements, together with excluded_middle, can be considered as characterizing classical logic. We can't prove any of them in Coq, but we can consistently add any one of them as an axiom if we wish to work in classical logic.
Definition peirce := ∀ P Q: Prop,
((P → Q) → P) → P.
Definition double_negation_elimination := ∀ P:Prop,
~~P → P.
Definition de_morgan_not_and_not := ∀ P Q:Prop,
~(~P ∧ ¬Q) → P ∨ Q.
Definition implies_to_or := ∀ P Q:Prop,
(P → Q) → (¬P ∨ Q).
(* FILL IN HERE *)
☐
((P → Q) → P) → P.
Definition double_negation_elimination := ∀ P:Prop,
~~P → P.
Definition de_morgan_not_and_not := ∀ P Q:Prop,
~(~P ∧ ¬Q) → P ∨ Q.
Definition implies_to_or := ∀ P Q:Prop,
(P → Q) → (¬P ∨ Q).
(* FILL IN HERE *)
☐
(* 2023-08-16 16:28 *)