(**
* Logic in Coq
-----
#*#
Topics:
- propositions and proofs
- [Prop] and [Set]
- propositional logic
- implication
- conjunction
- disjunction
- [False] and [True]
- negation
- equality and implication revisited
- tautologies
#*#
-----
(**********************************************************************)
** Propositions and proofs
Recall that the [Check] command type checks an expression and causes Coq
to output the type in the output window.
*)
Check list.
(** The type of [list] is [Type -> Type]. It takes a type as input and produces
a type as output. Thus, like OCaml's [list], Coq's [list] is a type
constructor. But unlike OCaml, the type of Coq's [list] contains [->],
indicating that it is a function. In Coq, [list] truly is a function that can
be applied: *)
Definition natlist : Type := list nat.
Check natlist.
(**
Think of Coq's [list] as a _type-level function_: it is a function that takes
types as inputs and produces types as outputs. OCaml doesn't have anything
exactly equivalent to that.
What, then, is the type of a theorem?
*)
Theorem obvious_fact : 1 + 1 = 2.
Proof. trivial. Qed.
Check obvious_fact.
(**
Coq responds:
<<
obvious_fact : 1 + 1 = 2
>>
So the type of [obvious_fact] is [1 + 1 = 2]. That might seem rather
mysterious. After all, [1 + 1 = 2] is definitely not an OCaml type. But Coq's
type system is far richer than OCaml's. In OCaml, we could think of [42 : int]
as meaning that [42] is a _meaningful_ expression of type [int]. There are
likewise _meaningless_ expressions---for example, [42+true] is meaningless in
OCaml, and it therefore cannot be given a type. The meaning of [int] in OCaml
is that of a computation producing a value that fits within 2^63 bits and can be
interpreted as an element of Z, the mathematical set of integers.
So what are meaningful Coq expressions of type [1 + 2 = 2]? They are the
_proofs_ of [1 + 1 = 2]. There might be many such proofs; here are two:
- reduce [1+1] to [2], then note that [x=x].
- subtract [1] from both sides, resulting in [1 + 1 - 1 = 2 - 1], reduce both
sides to [1], then note that [x = x].
Regardless of the exact proof, it is an _argument_ for, or _evidence_ for, the
assertion that [1 + 1 = 2]. So when we say that [obvious_fact : 1 + 1 = 2],
what we're really saying is that [obvious_fact] is a proof of [1 + 1 = 2].
Likewise, when we write [Definition x := 42.] in Coq, and then observe that [x :
nat], what we're saying is not just that [x] has type [nat], but also that there
is _evidence_ for the type [nat], i.e., that there do exist values of that type.
Many of them, in fact---just like there are many proofs of [1 + 1 = 2].
So now we have an explanation for what the type of [obvious_fact] is. But what
is its value? Let's ask Coq.
*)
Print obvious_fact.
(**
Coq responds:
<<
obvious_fact = eq_refl : 1 + 1 = 2
>>
So what is [eq_refl]?
*)
Print eq_refl.
(** Amidst all the output that produces, you will spy
<<
eq_refl : x = x
>>
That is, [eq_refl] has type [x = x]. In other words, [eq_refl] asserts
something is always equal to itself, a property known as the _reflexivity of
equality_.
So the proof of [1 + 1 = 2] in Coq is really just that equality
is reflexive. It turns out that Coq evaluates expressions before applying that
fact, so it evaluates [1+1] to [2], resulting in [2 = 2], which holds by
reflexivity. Thus the proof we found above, using the [trivial] tactic,
corresponds to the first of the two possible proofs we sketched.
*)
(**********************************************************************)
(**
** [Prop] and [Set]
We've now seen that there are programs and proofs in Coq. Let's investigate
their types. We already know that [42], a program, has type [nat], and that
[eq_refl : 1 + 1 = 2]. But let's now investigate what the types of [nat] and [1
+ 1 = 2] are. First, [nat]:
*)
Check nat.
(**
Coq says that [nat : Set]. Here, [Set] is a predefined keyword in Coq that we
can think of as meaning all program data types. Further, we can ask what the
type of [Set] is:
*)
Check Set.
(**
Coq says that [Set : Type], so [Set] is a type. The Coq documentation
describes [Set] as being the type of _program specifications_, which describe
computations. For example,
- [42] specifies a computation that simply returns [42]. It's a specification
because [42 : nat] and [nat : Set].
- [fun x:nat => x+1] specifies a computation that increments a natural number.
It's a specification because it has type [nat -> nat], and [nat -> nat : Set].
- We could also write more complicated specifications to express computations
such as list sorting functions, or binary search tree lookups.
Next, let's investigate what the type of [1 + 1 = 2] is.
*)
Check 1 + 1 = 2.
(**
Coq says that [1 + 1 = 2 : Prop]. This is the type of _propositions_, which are
logical formulas that we can attempt to prove. Note that propositions are not
necessarily provable though. For example, [2110 = 3110] has type [Prop], even
though it obviously does not hold. What is the type of [Prop]?
*)
Check Prop.
(**
Coq says that [Prop : Type]. So [Prop] is also a type. So far we have seen one
way of creating a proposition, by using the equality operator. We could attempt
to learn more about that operator with [Check =.], but that will result in an
error: [Check] doesn't work on this kind of notation, and there isn't something
like in OCaml where we can wrap an operator in parentheses. Instead, we need to
find out what function name that operator corresponds to. The command for that
is [Locate].
*)
Locate "=".
(**
Coq tells us two possible meanings for [=], and the second is what we want:
<<
"x = y" := eq x y
>>
That means that anywhere [x = y] appears, Coq understands it as the function
[eq] applied to [x] and [y]. Now that we know the name of that function, we can
check it:
*)
Check eq.
(**
The output of that is a bit mysterious because of the [?A] that shows up in it.
What's going on is that [eq] has an implicit type argument. (Implicit arguments
were discussed in the previous set of notes.) If we prefix [eq] with [@] to
treat the argument as explicit, we can get more readable output.
*)
Check @eq.
(**
Coq says that
<<
@eq : forall A : Type, A -> A -> Prop
>>
In other words, [@eq] takes a type argument [A], a value of type [A], another
value of type [A], and returns a proposition, which is the proposition asserting
the equality of those two values. So:
- [@eq nat 42 42] asserts the equality of [42] and [42], i.e., [42 = 42]. So
does [eq 42 42], where [nat] is implicit.
- [@eq nat 2110 3110] asserts the equality of [2110] and [3110].
So does [eq 2110 3110] and [2110=3110]. Of course they aren't equal, but
we're still allowed to form such a proposition, even if it isn't provable.
- [@eq (nat->nat) (fun x => x+1) (fun n => 1+n)] asserts the equality of two
syntactically different increment functions, as does [eq (fun x => x+1)
(fun n => 1+n)] and [(fun x => x+1) = (fun x => 1+n)].
*)
(**********************************************************************)
(**
** Propositional logic
The standard propositional logic _connectives_ (ways of syntactically connecting
propositions together) are:
- Implication: [P -> Q]. In English we usually express this connective as
"if [P], then [Q]" or "P implies Q".
- Conjunction: [P /\ Q]. In English we usually express this connective as
"P and Q".
- Disjunction: [P \/ Q]. In English we usually express this connective as
"P or Q". Keep in mind that this is the sense of the word "or" that
allows one or both of [P] and [Q] to hold, rather than exactly one.
- Negation: [~P]. In English we usually express this connective as
"not P".
All of these are ways of creating propositions in Coq. Implication is so
primitive that it's simply "baked in" to Coq. But the other connectives are
defined as part of Coq's standard library under the very natural names of [and],
[or], and [not].
In addition to those connectives, there are two propositions [True] and
[False] which always and never hold, respectively. And we can have
variables representing propositions. Idiomatically, we usually choose
names like [P], [Q], [R], ... for those variables.
*)
Check and.
Check or.
Check not.
(**
<<
and : Prop -> Prop -> Prop
or : Prop -> Prop -> Prop
not : Prop -> Prop
>>
Both [and] and [or] take two propositions as input and return a proposition;
[not] takes one proposition as input and returns a proposition.
At this point, you might have noticed that [->] seems to be overloaded:
- [t1 -> t2] is the type of functions that take an input of type [t1] and
return an output of type [t2].
- [P -> Q] is an proposition that asserts [P] implies [Q].
There is a uniform way to think about both uses of [->], which is as a
_transformer_. A function of type [t1 -> t2] transforms a value of type [t1]
into a value of type [t2]. An implication [P -> Q] in a way transforms [P] into
[Q]: if [P] holds, then [Q] also holds; or better yet, a proof of [P -> Q] can
be thought of as a function that transforms evidence for [P] into evidence for
[Q].
Let's do some proofs with these connectives to get a better sense of
how they work.
(**********************************************************************)
** Implication
Let's try one of the simplest possible theorems we could prove using
implication: P implies P.
*)
Theorem p_implies_p : forall P:Prop, P -> P.
(**
That proposition says that for any proposition [P], it holds that [P] implies
[P].
Intuitively, why should be able able to prove this proposition? That is, what
is an argument you could give to another human? We always encourage you to try
to answer that question before launching into a Coq proof---for the same reason
your introductory programming instructor always encouraged you to think about
programs before you begin typing them.
So why does this proposition hold? It's rather trivial, really. If [P] holds,
then certainly [P] holds. That is, [P] implies itself. An example in English
could be "if 3110 is fun, then 3110 is fun." If you've already assumed [P], then
necessarily [P] follows from your assumptions: that's what it means to be an
assumption.
The Coq proof below uses that reasoning.
*)
Proof.
intros P.
intros P_assumed.
assumption.
Qed.
(** The second step, [intros P_assumed], is a new use for the [intros] tactic.
This usage peels off the left-hand side of an implication and puts it into the
proof assumptions. The third step, [assumption] is a new tactic. It finishes a
proof of a subgoal [G] whenever [G] is already an assumption in the proof.
Let's look at the type of [p_implies_p].
*)
Check p_implies_p.
(**
Coq says [p_implies_p : forall P : Prop, P -> P]. So [p_implies_p] is proof
of, or evidence for, [forall P : Prop, P -> P], as we discussed above.
What is that evidence? We can use [Print] to find out:
*)
Print p_implies_p.
(**
Coq responds
<<
p_implies_p =
fun (P : Prop) (P_assumed : P) => P_assumed
: forall P : Prop, P -> P
>>
Let's pull that apart. Coq says that [p_implies_p] is a name that is
bound to a value and has a type. The type we already know. The value
is perhaps surprising: it is a function! Actually, maybe it shouldn't
be surprising given the discussion we had above about transformers.
[P -> P] should transform evidence for [P] into evidence for [P]. Trivially,
evidence for [P] already _is_ evidence for [P], so there's nothing to be done.
And we can see that in the function itself:
- it takes in an argument named [P], which is the proposition; and
- it takes in another argument named [P_assumed] of type [P]. Since
[P_assumed] has a proposition as type, [P_assumed] must be evidence
for that proposition.
- the function simply returns [P_assumed]. That is, it returns the
evidence for [P] that was already passed into it as an argument.
Note that the names of the arguments to that function are the names
that we chose with the [intros] tactic.
Let's clarify a subtle piece of terminology, "proof". The proof of
[forall P:Prop, P -> P] is the anonymous function [fun (P : Prop)
(P_assumed : P) => P_assumed ]. That is, the proof of a proposition
[P] is a program that has type [P]. On the other hand, the commands
we used above
<<
Proof.
intros P.
intros P_assumed.
assumption.
Qed.
>>
are how we help Coq find that proof. We provide guidance using tactics,
and Coq uses those tactics to figure out how to construct the program.
So although it's tempting to refer to [intros P. intros P_assumed.
assumption.] as the "proof"---and we often will as a kind of shorthand
terminology---the proof is really the program that is constructed, not
the tactics that help do the construction.
It is, by the way, possible to just directly tell Coq what the proof is
by using the command [Definition]:
*)
Definition p_implies_p_direct : forall P:Prop, P -> P :=
fun p ev_p => ev_p.
(**
But rarely do we directly write down proofs, because for most propositions
they are not so trivial. Tactics are a huge help in constructing
complicated proofs.
Let's try another theorem. A _syllogism_ is a classical form of argument
that typically goes something like this:
- All humans are mortal.
- Socrates is a human.
- Therefore Socrates is mortal.
The first assumption in that proof, "all humans are mortal", is an implication:
if X is a human, then X is mortal. The second assumption is that
Socrates is a human. Putting those two assumptions together, we conclude
that Socrates is mortal.
We can formalize that kind of reasoning as follows:
*)
Theorem syllogism : forall P Q : Prop,
(P -> Q) -> P -> Q.
(**
How would you convince a human that this theorem holds? By the same
reasoning as above. Assume that [P -> Q]. Also assume [P]. Since
[P] holds, and since [P] implies [Q], we know that [Q] must also hold.
The Coq proof below uses that style of argument.
*)
Proof.
intros P Q evPimpQ evP.
apply evPimpQ.
assumption.
Qed.
(**
The first line of the proof, [intros P Q evPimpQ evP] introduces four
assumptions. The first two are the variables [P] and [Q]. The third is [P->Q],
which we give the name [evPimpQ] as a human-readable hint that it is evidence
that [P] implies [Q]. The fourth is [P], which we give the name [evP] as a hint
that we have assumed we have evidence for [P].
The second line of the proof, [apply evPimpQ], uses a new tactic [apply] to
apply the evidence that [P -> Q] to the goal of the proof, which is [Q]. This
transforms the goal to be [P]. Think of this as _backward reasoning_: we know
we want to show [Q], and since we have evidence that [P -> Q], we can work
backward to conclude that if we could only show [P], then we'd be done.
The third line concludes by pointing out to Coq that in fact we do
already have evidence for [P] as an assumption.
Let's look at the proof that these tactics cause Coq to create:
*)
Print syllogism.
(**
We see that
<<
syllogism =
fun (P Q : Prop) (evPimpQ : P -> Q) (evP : P) => evPimpQ evP
: forall P Q : Prop, (P -> Q) -> P -> Q
>>
Picking that apart, [syllogism] is a function that takes four arguments.
The third argument [evPimpQ] is of type [P -> Q]. Going back to our reading
of [->] in different ways, we can think of [evPimpQ] as
- a function that transforms something of type [P] into something of type [Q],
or
- evidence for [P -> Q],
or
- a transformer that takes in evidence of [P] and produces evidence of [Q].
The deep point here is: _all of these are really the same interpretation_.
There's no difference between them. The value [evPimpQ] is a function,
and it is evidence, and it is an evidence transformer.
We can see that [evPimpQ] is being used as a function in the body
[evPimpQ evP] of the anonymous function, above. It is applied to
[evP], which is the evidence for [P], thus producing evidence for [Q].
So "apply" really is a great name for the tactic: it causes a function
application to occur in the proof.
Let's try one more proof with implication.
*)
Theorem imp_trans : forall P Q R : Prop,
(P -> Q) -> (Q -> R) -> (P -> R).
(**
As usual, let's first try to give an argument in English. We assume
that [P -> Q] and [Q -> R], and we want to conclude [P -> R]. So suppose
that [P] did hold. Then from [P -> Q] we'd conclude [Q], and then from
[Q -> R] we'd conclude [R]. So there's a kind of chain of evidence here
from [P] to [Q] to [R]. This kind of chained relationship is called
_transitive_, as you'll recall from CS 2800. So what we're proving
here is that implication is transitive, hence the name [imp_trans].
*)
Proof.
intros P Q R evPimpQ evQimpR.
intros evP.
apply evQimpR.
apply evPimpQ.
assumption.
Qed.
(**
The second line, [intros evP], wouldn't actually need to be separated
from the first line; we could have introduced [evP] along with the rest.
We did it separately just so that we could see that it peels off the
[P] from [P -> R].
The third line, [apply evQimpR] applies the evidence that [Q -> R] to
the goal [R], causing the goal to become [Q]. Again, this is backward
reasoning: we want to show [R], and we know that [Q -> R], so if
we could just show [Q] we'd be done.
The fourth line, [apply evPimpQ], applies the evidence that [P -> Q]
to the goal [Q], causing the goal to become [P].
Finally, the fifth line, [assumption], finishes the proof by pointing
out that [P] is already an assumption.
Let's look at the resulting proof:
*)
Print imp_trans.
(**
Coq says
<<
imp_trans =
fun (P Q R : Prop) (evPimpQ : P -> Q) (evQimpR : Q -> R) (evP : P) =>
evQimpR (evPimpQ evP)
: forall P Q R : Prop, (P -> Q) -> (Q -> R) -> P -> R
>>
Drilling down into the body of that anonymous function we see
[evQimpR (evPimpQ evP)]. So we have [evPimpQ] being applied to [evP], which
transforms the evidence for [P] into the evidence for [Q]. Then we have
[evQimpR] applied to that evidence for [Q], thus producing evidence for [R]. So
there are two function applications. If Coq had the OCaml operator [|>], we
could rewrite that function body as [evP |> evPimpQ |> evQimpR], which might
make it even clearer what is going on: the chain of reasoning just takes [P] to
[Q] to [R].
(**********************************************************************)
** Conjunction
Now we turn our attention to the conjunction connective. Here's
a first theorem to prove.
*)
Theorem and_fst : forall P Q, P /\ Q -> P.
(** Why does that hold, intuitively? Suppose we have evidence for [P /\ Q].
Then we must have evidence for both [P] and for [Q]. The evidence for [P] alone
suffices to conclude [P]. As an example, if we have evidence that [x > 0 /\ y >
0], then we must have evidence that [x > 0]; we're allowed to forget about the
evidence for [y > 0].
Having established that intuition, let's create a proof with Coq.
*)
Proof.
intros P Q PandQ.
destruct PandQ as [P_holds Q_holds].
assumption.
Qed.
(**
The second line of that proof uses a familiar tactic in a new way.
Previously we used [destruct] to do case analysis, splitting apart
a [day] into the seven possible constructors it could have.
Here, we use [destruct] to split the evidence for [P /\ Q] into
its separate components. To give names to those two components,
we use a new syntax, [as [...]], where the names appearing in
the [...] are used as the names for the components. If we leave
off the [as] clause, Coq will happily choose names for us, but
they won't be human-readable, descriptive names. So it's good
style to pick the names ourselves.
After destructing the evidence for [PandQ] into evidence for
[P] and evidence for [Q], we can easily finish the proof
with [assumption].
Let's look at the proof of [and_fst].
*)
Print and_fst.
(**
Coq says that
<<
and_fst =
fun (P Q : Prop) (PandQ : P /\ Q) =>
match PandQ with
| conj P_holds _ => P_holds
end
: forall P Q : Prop, P /\ Q -> P
>>
Let's dissect that. We see that [and_fst] is a function that takes three
arguments. The third is evidence for [P /\ Q]. The function then pattern
matches against that evidence, providing just a single branch in the pattern
match. The pattern it uses is [conj P_holds _]. As in OCaml, [_] is a wildcard
that matches anything. The identifier [conj] is a constructor; unlike OCaml,
it's fine for constructors to begin with lowercase characters. So the pattern
matches against [conj] applied to two values, extracts the first value with the
name [P_holds], and doesn't give a name to the second value. The branch then
returns that argument [P_holds]. So we can already see that the function is
splitting apart the evidence into two pieces, and forgetting about one piece.
But to fully understand this, we need to know more about [/\] and [conj].
First, what is [/\]?
*)
Locate "/\".
(**
Coq says ["A /\ B" := and A B]. That is, [/\] is just an infix notation
for the [and] function. What is [and]?
*)
Print and.
(**
Coq says
<<
Inductive and (A B : Prop) : Prop :=
conj : A -> B -> A /\ B
>>
It might help to compare to lists.
*)
Print list.
(**
Coq says
<<
Inductive list (A : Type) : Type :=
nil : list A | cons : A -> list A -> list A
>>
In Coq, [Inductive] defines a so-called _inductive_ type, and provides its
constructors. In OCaml, the [type] keyword serves a similar purpose. So [list]
in Coq is a type with two constructors named [nil] and [cons]. Similarly, [and]
is a type with a single constructor named [conj], which is a function that takes
in a value of type [A], a value of type [B], and returns a value of type [A /\
B], which is just infix notation for [and A B]. Another way of putting that is
that [conj] takes in evidence of [A], evidence of [B], and returns evidence of
[and A B].
So there's only one way of producing evidence of [A /\ B], which is to
separately produce evidence of [A] and of [B], both of which are passed into
[conj]. That's why when we destructed [A /\ B], it had to produce both evidence
of [A] and of [B].
Going back to [and_fst]:
<<
and_fst =
fun (P Q : Prop) (PandQ : P /\ Q) =>
match PandQ with
| conj P_holds _ => P_holds
end
: forall P Q : Prop, P /\ Q -> P
>>
we have a function that pattern matches against [PandQ], extracts the
evidence for [P], forgets about the evidence for [Q], and simply returns
the evidence for [P].
Given all that, the following theorem and its program should be
unsurprising.
*)
Theorem and_snd : forall P Q : Prop,
P /\ Q -> Q.
Proof.
intros P Q PandQ.
destruct PandQ as [P_holds Q_holds].
assumption.
Qed.
Print and_snd.
(**
Coq responds:
<<
and_snd =
fun (P Q : Prop) (PandQ : P /\ Q) =>
match PandQ with
| conj _ Q_holds => Q_holds
end
: forall P Q : Prop, P /\ Q -> Q
>>
In that program the pattern match returns the second piece of evidence, which
shows [Q] holds, rather than the first, which would show that [P] holds.
Here is another proof involving [and]. *)
Theorem and_ex : 42=42 /\ 43=43.
(** Why does that hold, intuitively? Because equality is reflexive, regardless
of how many times we connect that fact with [/\]. *)
Proof.
split.
trivial. trivial.
Qed.
(** The first line of that proof, [split], is a new tactic. It splits a goal of
the form [P /\ Q] into two separate subgoals, one for [P], and another for [Q].
Both must be proved individually. In the proof above, [trivial] suffices to
prove them, because they are both trivial equalitiies.
What is [and_ex]? *)
Print and_ex.
(**
Coq responds:
<<
and_ex = conj eq_refl eq_refl
: 42 = 42 /\ 43 = 43
>>
So [and_ex] is [conj] applied to [eq_refl] as its first argument and [eq_refl]
as its second argument.
As another example of conjunction, let's prove that it is commutative. *)
Theorem and_comm: forall P Q, P /\ Q -> Q /\ P.
(**
Why does this hold? If we assume that [P /\ Q] holds, then separately we must
have evidence for [P] as well as [Q]. But the we could just assemble that
evidence in the opposite order, producing evidence for [Q /\ P]. That's what
the proof below does.
*)
Proof.
intros P Q PandQ.
destruct PandQ as [P_holds Q_holds].
split.
all: assumption.
Qed.
(**
There's nothing new in that proof: we split the evidence into two pieces using
pattern matching, then reassemble them in a different order. We can see that in
the program:
*)
Print and_comm.
(**
Coq responds:
<<
and_comm =
fun (P Q : Prop) (PandQ : P /\ Q) =>
match PandQ with
| conj P_holds Q_holds => conj Q_holds P_holds
end
: forall P Q : Prop, P /\ Q -> Q /\ P
>>
Note how the pattern match binds two variables, then returns the [conj]
constructor applied to those variables in the opposite order.
Here's one more proof involving implication and conjunction. *)
Theorem and_to_imp : forall P Q R : Prop,
(P /\ Q -> R) -> (P -> (Q -> R)).
(** Intuitively, why does this hold? Because we can assume [P], [Q] and [R]. as
well as that [P /\ Q -> R], and that we have evidence for [P] and [Q] already.
We can combine those two pieces of evidence for [P] and [Q] into a single piece
of evidence for [P /\ Q], which then yields the desired evidence for [R]. *)
Proof.
intros P Q R evPandQimpR evP evQ.
apply evPandQimpR.
split.
all: assumption.
Qed.
(** There are no new tactics in the proof above. In line 2, we use [apply] to
once again do backwards reasoning, transforming the goal of [R] into [P /\ Q].
Then we split that goal into two pieces, each of which can be solved by
assumption.
Let's look at the resulting program:
<<
and_to_imp=
fun (P Q : Prop) (PandQ : P /\ Q) =>
match PandQ with
| conj P_holds Q_holds => conj Q_holds P_holds
end
: forall P Q : Prop, P /\ Q -> Q /\ P
>>
That program matches against evidence for [P /\ Q], then swaps
the order of evidence around, thus producing evidence for [Q /\ P].
(**********************************************************************)
** Disjunction
Let's start with disjunction by proving a theorem very similar to the first
theorem we proved for conjunction: *)
Theorem or_left : forall (P Q : Prop), P -> P \/ Q.
(** As always, what's the intuition? Well, if we have evidence for [P], then we
have evidence for [P \/ Q], because we have evidence already for the left-hand
side of that connective.
Let's formalize that argument in Coq. *)
Proof.
intros P Q P_holds.
left.
assumption.
Qed.
(** The second line of that proof, [left], uses a new tactic that tells Coq we
want to prove the left-hand side of a disjunction. Specifically, the goal at
that point is [P \/ Q], and [left] tells Coq the throw out [Q] and just focus on
proving [P]. That's easy, because [P] is already an assumption.
Let's investigate the resulting program. *)
Print or_left.
(**
Coq responds
<<
or_left =
fun (P Q : Prop) (P_holds : P) => or_introl P_holds
: forall P Q : Prop, P -> P \/ Q
>>
That function's arguments are no mystery by now, but what is its body?
We need to find out more about [or_introl].
*)
Locate "\/".
Print or_introl.
(**
We learn that "\/" is infix notation for [or A B], and
[or_introl] is one of two constructors of the type [or]:
<<
Inductive or (A B : Prop) : Prop :=
or_introl : A -> A \/ B
| or_intror : B -> A \/ B
>>
Those constructors take evidence for either the left-hand side or right-hand
side of the disjuncation. So the body of [or_left] is just taking evidence for
[A] and using [or_introl] to construct a value with that evidence.
Similarly, the following theorem constructs proof using evidence for the
right-hand side of a disjunction. *)
Theorem or_right : forall P Q : Prop, Q -> P \/ Q.
(** Much like with [or_left], this intuitively holds because evidence for [Q]
suffices as evidence for [P \/ Q]. *)
Proof.
intros P Q Q_holds.
right.
assumption.
Qed.
Print or_right.
(**
The resulting program uses the constructor [or_intror]:
<<
or_right =
fun (P Q : Prop) (Q_holds : Q) => or_intror Q_holds
: forall P Q : Prop, Q -> P \/ Q
>>
We could use those theorems to prove some related theorems.
For example, [3110 = 3110] implies that [3110 = 3110 \/ 2110 = 3110].
*)
Theorem or_thm : 3110 = 3110 \/ 2110 = 3110.
(**
Why does that hold? Because the left-hand side holds---even though
the right hand side does not.
*)
Proof.
left. trivial.
Qed.
Print or_thm.
(**
Coq responds that
<<
or_thm = or_introl eq_refl
: 3110 = 3110 \/ 2110 = 3110
>>
In other words, the theorem is proved by applying the [or_introl] constructor to
[eq_refl]. It matters, though, that we provided Coq with the guidance to prove
the left-hand side. If we had chosen the right-hand side, we would have been
stuck trying to prove that [2110 = 3110], which of course it does not.
Next, let's prove that disjunction is commutative, as we did for conjunction
above. *)
Theorem or_comm : forall P Q, P \/ Q -> Q \/ P.
(** Why does this hold? If you assume you have evidence for [P \/ Q], then you
either have evidence for [P] or evidence for [Q]. If it's [P], then you can
prove [Q \/ P] by providing evidence for the right-hand side; or if it's [Q],
for the left-hand side.
That's what the Coq proof below does. *)
Proof.
intros P Q PorQ.
destruct PorQ as [P_holds | Q_holds].
- right. assumption.
- left. assumption.
Qed.
(** In the second line of that proof, we destruct the disjunction [PorQ] with a
slightly different syntax than when we destructed conjunction above. There's
now a vertical bar. The reason for that has to do with the definitions of [and]
and [or].
The definition of [and] had just a single constructor:
<<
conj : A -> B -> A /\ B
>>
So when we wrote [destruct PandQ as [P_holds Q_holds]], we were essentially
writing a pattern match against that single constructor [conj], and binding its
[A] argument to the name [P_holds], and its [B] argument to the name [Q_holds].
But the definition of [or] has two constructors:
<<
or_introl : A -> A \/ B
| or_intror : B -> A \/ B
>>
So when we write [destruct PorQ as [P_holds | Q_holds]], we're providing two
patterns: the first matches against the first constructor [or_introl], binding
its [A] argument to the name [P_holds]; and the second against the second
constructor [or_intror], binding its [B] argument to [Q_holds].
What makes this confusing to an OCaml programmer is that the [as] clause doesn't
actually name the constructors. It might be clearer if Coq's [destruct] tactic
used the following hypothetical syntax:
<<
destruct PandQ as [conj P_holds Q_holds].
destruct PorQ as [or_introl P_holds | or_intror Q_holds].
>>
but that's just not how [destruct..as] works.
After the destruction occurs, we get two new subgoals to prove, corresponding to
whether [PorQ] matched [or_introl] or [or_intror]. In the third line of the
proof, which corresponds to [PorQ] matching [or_introl], we have evidence for
[P], so we choose to prove the right side of [Q \/ P] by assumption. The fourth
line does a similar thing, but using evidence for [Q] hence proving the left
side of [Q \/ P].
Let's look at the resulting proof. *)
Print or_comm.
(**
Coq responds
<<
or_comm =
fun (P Q : Prop) (PorQ : P \/ Q) =>
match PorQ with
| or_introl P_holds => or_intror P_holds
| or_intror Q_holds => or_introl Q_holds
end
: forall P Q : Prop, P \/ Q -> Q \/ P)
>>
That function takes an argument [PorQ] is evidence for [P \/ Q], pattern matches
against that argument, extracts the evidence for either [P] or [Q], then returns
that evidence wrapped in the "opposite" constructor: evidence that came in in
the left constructor is returned with the right constructor, and vice versa.
Next, let's prove a theorem involving both conjunction and disjunction. *)
Theorem or_distr_and : forall P Q R,
P \/ (Q /\ R) -> (P \/ Q) /\ (P \/ R).
(** This theorem says that "or" distributes over "and". Intuitively, if we have
evidence for [P \/ (Q /\ R)], then one of two things holds: either we have
evidence for [P], or we have evidence for [Q /\ R].
- If we have evidence for [P], then we use that evidence to create evidence both
for [P \/ Q] and for [P /\ R].
- If we have evidence for [Q /\ R], then we split that evidence apart into
evidence separately for [Q] and for [R]. We use the evidence for
[Q] to create evidence for [P \/ Q], and likewise the evidence for [R] to
create evidence for [P \/ R].
The Coq proof below follows that intuition. *)
Proof.
intros P Q R PorQR.
destruct PorQR as [P_holds | QR_holds].
- split.
+ left. assumption.
+ left. assumption.
- destruct QR_holds as [Q_holds R_holds].
split.
+ right. assumption.
+ right. assumption.
Qed.
(** We used _nested bullets_ in that proof to keep the structure of the proof
clear to the reader, and to make it easier to follow the proof in the proof
window when we step through it.
But you'll notice there's a lot of repetition in the proof. We can eliminate
some by using the [;] tactical (discussed in the previous notes) to chain
together some proof steps. *)
Theorem or_distr_and_shorter : forall P Q R,
P \/ (Q /\ R) -> (P \/ Q) /\ (P \/ R).
Proof.
intros P Q R PorQR.
destruct PorQR as [P_holds | QR_holds].
- split; left; assumption.
- destruct QR_holds as [Q_holds R_holds].
split; right; assumption.
Qed.
Print or_distr_and.
(**
Either way, the resulting proof is
<<
or_distr_and =
fun (P Q R : Prop) (PorQR : P \/ Q /\ R) =>
match PorQR with
| or_introl P_holds => conj (or_introl P_holds) (or_introl P_holds)
| or_intror QR_holds =>
match QR_holds with
| conj Q_holds R_holds => conj (or_intror Q_holds) (or_intror R_holds)
end
end
: forall P Q R : Prop, P \/ Q /\ R -> (P \/ Q) /\ (P \/ R)
>>
The nested pattern match in that proof corresponds to the nested [destruct]
above. Note that Coq omits some parentheses around conjunction, because it is
defined to have higher precedence than disjunction---just like [*] has higher
precedence than [+].
(**********************************************************************)
** [False] and [True]
[False] is the proposition that can never hold: we can never actually have
evidence for it. If we did somehow have evidence for [False], our entire system
of reasoning would be broken. There's a Latin phrase often used for that idea:
_ex falso quodlibet_, meaning "from false, anything". In English it's sometimes
known as the _Principle of Explosion_: if you're able to show [False], then
everything just explodes, and in fact anything at all becomes provable.
Here's the definition of [False]: *)
Print False.
(**
Coq responds
<<
Inductive False : Prop :=
>>
No, that isn't a typo above. There is nothing on the right-hand side of the
[:=]. The definition is saying that [False] is an inductive type, like [and]
and [or], but it has zero constructors. Since it has no constructors, we can
never create a value of type [False]---and that means we can never create
evidence that [False] holds.
Let's prove the Principle of Explosion. *)
Theorem explosion : forall P:Prop, False -> P.
(** Why should this theorem hold? Because of the intuition we gave above
regarding _ex falso_. *)
Proof.
intros P false_holds.
contradiction.
Qed.
(** The second line of the proof uses a new tactic, [contradiction]. This
tactic looks for any contradictions or assumptions of [False], and uses those to
conclude the proof. In this case, it immediately finds [False] as an assumption
named [false_holds].
The proof that Coq finds for [explosion] given the above tactics is a little bit
hard to follow because it uses two functions [False_ind] and [False_rect] that
are already defined for us. Their definitions are given below, but it's okay if
you want to skip over reading them at first.
<<
explosion =
fun (P : Prop) (false_holds : False) => False_ind P false_holds
: forall P : Prop, False -> P
False_ind = fun P : Prop => False_rect P
: forall P : Prop, False -> P
False_rect =
fun (P : Type) (f : False) => match f return P with
end
: forall P : Type, False -> P
>>
(The [return P] in the pattern match above is a type annotation: it says
that the return type of the entire [match] expression is [P].)
Note that [False_ind] just calls [False_rect], and [False_rect] immediately does
a pattern match. So we could simplify the proof of [explosion] by just directly
writing that pattern match ourselves. Let's do that using the [Definition]
command we saw above.
*)
Definition explosion' : forall (P:Prop), False -> P :=
fun (P : Prop) (f : False) =>
match f with
end.
(**
The proof of [explosion'] is a function that takes two inputs, a proposition
[P] and a value [f] of type [False]. As usual, we should understand that second
argument as being evidence for [False]. But it should be impossible to
construct such evidence! And indeed it is, because [False] has no constructors.
So we have a function that we could never actually apply.
In the body of that function is a pattern match against [f]. That pattern match
has zero branches in it, exactly because [False] has zero constructors. There is
nothing that could ever possibly match [f].
The return type of the function is [P], meaning it purportedly is evidence for
[P]. But of course no such evidence is ever constructed by the function, nor
coud it be. So it's a good thing that it's impossible to apply the function.
Were it possible, it would have to fabricate evidence for any proposition [P]
whatsoever---which is not possible.
We can never prove [P -> P /\ False], because there is no way to construct the
evidence for the right side. *)
Theorem p_imp_p_and_false : forall P:Prop, P -> P /\ False.
Proof.
intros P P_holds. split. assumption. (* now we're stuck *)
Abort.
(** But we can always prove [P -> P \/ False] by just focusing on the non-false
part of the disjunction. *)
Theorem p_imp_p_or_false : forall P:Prop, P -> P \/ False.
Proof.
intros P P_holds. left. assumption.
Qed.
(** [True] is the proposition that is always true, i.e., for which we can always
provide. It is defined by Coq as an inductive type: *)
Print True.
(**
Coq responds:
<<
Inductive True : Prop := I : True
>>
So [True] has a single constructor [I], and anywhere we write [I], that provides
evidence for [True]. Let's redo the two theorem we just did for [False], but
with [True] in place of [False]. *)
Theorem p_imp_p_and_true : forall P:Prop, P -> P /\ True.
Proof.
intros P P_holds. split. assumption. exact I.
Qed.
(** The final tactic in that proof, [exact], can be used whenever you already
know exactly the program expression you want to write to provide evidence for
the goal you are trying to prove. In this case, we know that [I] always
provides evidence for [true]. Instead of [exact] we could also have used
[trivial], which is capable of proving trivial propositions like [True]. *)
Theorem p_imp_p_or_true : forall P:Prop, P -> P \/ True.
Proof.
intros P P_holds. left. assumption.
Qed.
(**
(**********************************************************************)
** Negation
The negation connective can be the trickiest to work with in Coq. Let's start
by seeing how it is defined. *)
Locate "~".
Print not.
(**
Coq responds
<<
not = fun A : Prop => A -> False
: Prop -> Prop
>>
Unlike conjunction and disjunction, negation is defined as a function, rather
than an inductive type. Anywhere we write [~P], it really means [not P], which
is [(fun A => A -> False) P], which reduces to [P -> False]. In short, [~P]
is effectively syntactic sugar for [P -> False].
Here are two proofs involving negation, so that we can get used to this
definition of it. *)
Theorem notFalse : ~False -> True.
(** Intuition: anything implies [True], regardless of what that anything is. *)
Proof.
unfold not.
intros.
exact I.
Qed.
(** The first line of that proof, [unfold not], is a new tactic, which replaces
the [~] in the goal with its definition and simplifies it. So [~False] becomes
[False -> False]. The second line uses a familiar tactic in a new way. We don't
provide any names to [intros], which causes Coq to choose its own names.
Normally we consider it good style to choose them ourselves, but here, we don't
care what they are, because we are never going to use them. Finally, the third
line uses [I] to prove [True].
Looking at the actual program produced, we see that it's very simple:
*)
Print notFalse.
(**
Coq responds
<<
notFalse = fun _ : False -> False => I
>>
which is a function that takes an argument that is never used, and simply
returns [I], which is the evidence for [True].
Here's a second proof involving negation. *)
Theorem notTrue: ~True -> False.
(** Intuition: if [True] implies [False], and if [True], then [False]. *)
Proof.
unfold not.
intros t_imp_f.
apply t_imp_f.
exact I.
Qed.
(** The first line of the proof replaces [~True] with [True -> False]. The rest
of the proof proceeds by moving [True -> False] into the assumptions, then
applying it to do backward reasoning, leaving us with needing to prove [True].
That holds by the [I] constructor.
The program that proof produces is interesting: *)
Print notTrue.
(**
Coq responds
<<
notTrue = fun t_imp_f : True -> False => t_imp_f I
: ~ True -> False
>>
That proof is actually a higher-order function that takes in a function
[t_imp_f] and applies that function to [I], thus transforming evidence
for [True] into evidence for [False], and returning that evidence. If
that seems impossible, it is! We'll never be able to apply this function,
because we'll never be able to construct a value to pass to it whose
type is [~True], i.e., [True -> False].
Next, let's return to the idea of explosion. From a contradiction we should
be able to derive anything at all. One kind of contradiction is for a
proposition and its negation to hold simultaneously, e.g., [P /\ ~P].
*)
Theorem contra_implies_anything : forall P Q, P /\ ~P -> Q.
(** Intuition: principle of explosion *)
Proof.
unfold not.
intros P Q PandnotP.
destruct PandnotP as [P_holds notP_holds].
contradiction.
Qed.
(** This proof proceeds along familiar lines: We destruct the evidence of [P /\
~P] into two pieces. That leaves us with [P] as an assumption as well as [~P].
The [contradiction] tactic detects those contradictory assumptions and finished
the proof. By the way, we could have left out the [as] clause in the [destruct]
here, since we are never going to use the names we chose, but they will help
make the following program more readable: *)
Print contra_implies_anything.
(**
Coq responds:
<<
contra_implies_anything =
fun (P : Prop) (Q : Type) (PandnotP : P /\ (P -> False)) =>
match PandnotP with
| conj P_holds notP_holds => False_rect Q (notP_holds P_holds)
end
: forall (P : Prop) (Q : Type), P /\ ~ P -> Q
>>
The really interesting part of this is the body of the pattern match,
[False_rect Q (notP_holds P_holds)]. It applies [notP_holds] to [P_holds], thus
transforming evidence for [P] into evidence for [False]. It passes that
hypothetical evidence for [False] to [False_rect], which as we've seen before
uses such evidence to produce evidence for anything at all that we would
like---in this case, [Q], its first argument.
Next, let's try a proof involving all the connectives we've seen: negation,
conjunction, disjunction, and implication. The following theorem shows how
negation distributes over disjunction, and in so doing, produces a conjunction.
The name we choose for this theorem is traditional and gives credit to Augustus
De Morgan, a 19th century logician, even though the theorem was known far
earlier in history. *)
Theorem deMorgan : forall P Q : Prop,
~(P \/ Q) -> ~P /\ ~Q.
(** Intuition: if evidence for [P] or [Q] would lead to an explosion (i.e.,
[False]), then evidence for [P] would lead to an explosion, and evidence
for [Q] would also lead to an explosion. *)
Proof.
unfold not.
intros P Q PorQ_imp_false.
split.
- intros P_holds. apply PorQ_imp_false. left. assumption.
- intros Q_holds. apply PorQ_imp_false. right. assumption.
Qed.
(** Nothing we've done in that proof is new, but it is longer than any of our
proofs so far. The same is true of the program it produces: *)
Print deMorgan.
(**
Coq responds:
<<
deMorgan =
fun (P Q : Prop) (PorQ_imp_false : P \/ Q -> False) =>
conj (fun P_holds : P => PorQ_imp_false (or_introl P_holds))
(fun Q_holds : Q => PorQ_imp_false (or_intror Q_holds))
: forall P Q : Prop, ~ (P \/ Q) -> ~ P /\ ~ Q
>>
There is a second "De Morgan's Law", which says that negation distributes over
conjunction, thus producing a disjunction. But something seemingly goes very
wrong when we try to prove it in Coq: *)
Theorem deMorgan2 : forall P Q : Prop,
~(P /\ Q) -> ~P \/ ~Q.
(** Intuition: if evidence for P and Q would produce an explosion, then
either evidence for P would produce an explosion, or evidence for Q would. *)
Proof.
unfold not.
intros P Q PQ_imp_false.
left.
intros P_holds. apply PQ_imp_false. split. assumption.
Abort.
(** When we get to the point in the proof above where we need to prove [Q], we
have no means to do so. (The same problem would occur for [P] if instead of
[left] we had gone [right].) Why does this happen? One reason is that the
intuition we gave above is wrong! There's no reason that "if evidence for P and
Q would produce an explosion" implies "either evidence for P would produce an
explosion, or evidence for Q would". It's the combined evidence for [P] and [Q]
that produces the explosion in that assumption---nothing is said about evidence
for them individually.
But a deeper reason is that this theorem simply doesn't hold in Coq's logic:
there isn't any way to prove it. That might surprise you if you've studied some
logic before, and you were taught that both De Morgan laws are sound reasoning
principles. Indeed they are in some logics, but not in others.
In _classical logic_, which is what you almost certainly would have studied
before (e.g., in CS 2800), we are permitted to think of every proposition as
having a _truth value_, which is either [True] or [False]. And one way to prove
a theorem is to construct a _truth table_ showing what truth value a proposition
has for any assignment of [True] or [False] to its variables. For example
(writing [T] and [F] as abbreviations):
<<
P Q ~(P/\Q) (~P \/ ~Q) (~(P/\Q) -> (~P \/ ~Q))
T T F F T
T F T T T
F T T T T
F F T T T
>>
For every possible assignment of truth values to [P] and [Q], we get that the
truth value of [~(P/\Q) -> (~P \/ ~Q)] is [True]. So in classical logic,
[~(P/\Q) -> (~P \/ ~Q)] is a theorem.
But Coq uses a different logic called _constructive logic_. Constructive logic
is more convervative than classical logic, in that it always requires evidence
to be produced as part of a proof. As we saw above when we tried to prove
[deMorgan2], there just isn't a way to construct evidence for [~P \/ ~Q] out of
evidence for [~(P/\Q)].
There are many other propositions that are provable in classical logic but not
in constructive logic. Another pair of such propositions involves _double
negation_: [P -> ~~P] is provable in both logics, but [~~P -> P] is provable in
classical logic and not provable in constructive logic. Let's try proving both
just to see what happens.
*)
Theorem p_imp_nnp : forall P:Prop, P -> ~~P.
(** Intuition: ~~P is (P -> False) -> False. So the theorem could be
restated as P -> (P -> False) -> False. That's really just a syllogism,
with the first two arguments in the opposite order:
- P implies False.
- P holds.
- Therefore False holds. *)
Proof.
unfold not.
intros P evP evPimpFalse.
apply evPimpFalse.
assumption.
Qed.
(** We could make that intuition even more apparent by proving a version of the
syllogism theorem with its first two arguments swapped: *)
Theorem syllogism' : forall P Q : Prop,
P -> (P -> Q) -> Q.
Proof.
intros P Q evP evPimpQ.
apply evPimpQ.
assumption.
Qed.
(** Now we use [syllogism'] to prove [P -> ~~P]: *)
Theorem p_imp_nnp' : forall P:Prop, P -> ~~P.
Proof.
unfold not. intros P. apply syllogism'.
Qed.
(** In that proof, we have a new use for the [apply] tactic: we use it with the
name of a theorem we've already proved, because our goal is in fact that
theorem. In the resulting program, we saw this indeed becomes an application of
the [syllogism'] function: *)
Print p_imp_nnp'.
(**
Coq responds:
<<
p_imp_nnp' =
fun P : Prop => syllogism' P False
: forall P : Prop, P -> ~ ~ P
>>
Now let's try the other direction for double negation:
*)
Theorem nnp_imp_p : forall P : Prop, ~~P -> P.
(* intuition: actually it doesn't hold *)
Proof.
unfold not.
intros P evNNP.
Abort.
(** Once we get past introducing assumptions in that proof, we're stuck. There's
nothing we can do with [(P -> False) -> False] to prove [P]. Why? Because in
constructive logic, to prove [P], we must produce evidence for [P]. Nothing in
[(P -> False) -> False] gives us such evidence.
That's very different from classical logic, where we could just construct a
truth table:
<<
P ~~P (~~P -> P)
T T T
F F T
>>
Here's another even more brutally perplexing proposition that's provable in
classical logic but not in constructive logic. It's called _excluded middle_,
because it says that every proposition or its negation must hold; there is no
"middle ground": *)
Theorem excluded_middle : forall P, P \/ ~P.
Proof.
intros P.
left.
Abort.
(** Whether we go left or right in the second step of that proof, we immediately
get stuck. If we go left, Coq challenges us to construct evidence for [P], but
we don't have any. If we go right, Coq challenges us to construct evidence for
[P -> False], but we don't have any.
Yet in classical logic, excluded middle is easily proved by a truth table:
<<
P ~P (P \/ ~P)
T F T
F T T
>>
Why does Coq use constructive logic rather than classical logic? The reason
goes back to why we started looking at Coq, namely, program verification. We'd
like to be able to extract verified programs. Well, there simply is no program
whose type is [P \/ ~P], because such a program would have to use either
[or_introl] or [or_intror] to construct its result, and it would have to
magically guess which one to use, then magically somehow produce the appropriate
evidence.
Nonetheless, if all you want to do is reasoning in classical logic, and you
don't care about extracting verified programs, Coq does support that in a
library [Coq.Logic.Classical]. We'll load that library now in a nested [Module],
which restricts its influence just to that module so that we don't pollute the
rest of this file. *)
Module LetsDoClassicalReasoning.
Require Import Coq.Logic.Classical.
Print classic.
(**
Coq responds:
<<
*** [ classic : forall P : Prop, P \/ ~ P ]
>>
The [***] indicates that [classic] is an _axiom_ that the library simply asserts
without proof. Using that axiom, all the usual theorems of classical logic can
be proved, such as double negation: *)
Print NNPP.
(**
Coq responds:
<<
NNPP =
fun (p : Prop) (H : (p -> False) -> False) =>
or_ind (fun H0 : p => H0) (fun NP : ~ p => False_ind p (H NP)) (classic p)
: forall p : Prop, ~ ~ p -> p
>>
You can see on the last line that [NNPP] proves [~~p -> p], which we couldn't
prove above. And you'll see that [classic] shows up in the program to magically
construct evidence of [p \/ ~p]. *)
End LetsDoClassicalReasoning.
(**
(**********************************************************************)
** Equality and implication
Let's return to the two connectives with which we started, equality and
implication. Earlier we didn't explain them fully, but now we're equipped to
appreciate their definitions.
Recall how Coq defines equality:
*)
Locate "=".
Print eq.
(**
Coq responds:
<<
Inductive eq (A : Type) (x : A) : A -> Prop :=
eq_refl : x = x
For eq: Argument A is implicit ...
>>
Reading that carefully, we see that [eq] is parameterized on
- a type [A], and
- a value [x] whose type is [A].
We also see that [A] is implicit, so let's use [@eq] from now on in our
discussion just to be clear about [A].
When we apply [@eq] to a type [A] and a value [x], we get back a function of
type [A -> Prop]. The idea is that function will take its argument, let's call
it [y], and construct the proposition that asserts that [x] and [y] are equal.
For example: *)
Definition eq42 := @eq nat 42.
Check eq42.
Check (eq42 42).
Check (eq42 43).
(**
<<
eq42 : nat -> Prop
eq42 42 : Prop
eq42 43 : Prop
>>
There's only one way to construct a value of type [eq], though, and that's with
the [eq_refl] constructor. If we "desugar" the [=] notation, that constructor
has type [@eq A x x], where [x] must be of type [A]. *)
Check @eq_refl nat 42.
(**
<<
@eq_refl nat 42 : 42 = 42
>>
Note how the constructor above takes just a single argument of type [nat], not
two arguments: it will only ever show that argument is equal to itself, never
to anything else. There's literally no way to write an expression using
[eq_refl] to construct evidence that (e.g.) [42] and [43] are equal.
So instead of using [trivial], we could directly use [eq_refl] as a constructor
to prove equalities, much like we directly used [I] as a constructor to prove
[True] earlier in this file: *)
Theorem direct_eq : 42 = 42.
Proof.
exact (eq_refl 42).
Qed.
(** Equality, therefore, is not something that has to be "baked in" to Coq, but
rather something that is definable as an inductive type---much like [and] and
[or].
And now back to implication. It, too, is defined. *)
Locate "->".
(**
Coq responds:
<<
"A -> B" := forall _ : A, B
>>
That is, [A -> B] is really just syntactic sugar for [forall (_:A), B], where
we've added some parentheses for a little bit more clarity. Still, that
expression is tricky to read because of the wildcard in it. It might help if we
made [A] and [B] concrete, for example, if [A] is [P /\ Q] and [B] is [Q], where
[P] and [Q] are propositions. Then we could think of the type [forall (_ : P /\
Q), Q] as follows:
- [(_ : P /\ Q)] means an unnamed value of type [P /\ Q]. Using the evidence
interpretation we've been developing throughout this file, that value would be
evidence that [P /\ Q] holds. It's unnamed because it's not used on the
right-hand side.
- A value of type [Q] would be evidence for [Q].
- So a value of type [forall (_ : P /\ Q), Q] would be a "thing" that
for any piece of evidence that [P /\ Q] holds can produce evidence
that [Q] holds.
What is such a "thing"? A function! It transforms evidence for one proposition
into evidence for another proposition.
So of all the logic we've coded up in Coq in this file, the only truly primitive
pieces are [Inductive] definitions and [forall] types. Everything
else---equality, implication, conjunction, disjunction, true, false,
negation---can all be expressed in terms of those two primitives.
And although we had to learn many tactics, every proof we constructed with them
was really just a program that used functions, application, and pattern
matching.
(**********************************************************************)
** Tautologies
(Or, "How to make the computer do your CS 2800 logic homework for you".)
We needed to learn the tactics and definitions above for the more complicated
proofs we will later want to do in Coq. But if all you care about is proving
simple logical propositions, there's a tactic for that: [tauto]. It will
succeed in finding a proof of any propositional _tautology_, which is a formula
that must always hold regardless of the values of variables in it.
For example, one of the most complicated proofs we did above was De Morgan's
law. Here it is in just one tactic: *)
Theorem deMorgan' : forall P Q : Prop,
~(P \/ Q) -> ~P /\ ~Q.
Proof.
tauto.
Qed.
Print deMorgan'.
(**
Coq responds:
<<
deMorgan' =
fun (P Q : Prop) (H : ~ (P \/ Q)) =>
let H0 := (fun H0 : P => H (or_introl H0)) : P -> False in
let H1 := (fun H1 : Q => H (or_intror H1)) : Q -> False in
conj (fun H2 : P => let H3 := H0 H2 : False in False_ind False H3)
(fun H2 : Q => let H3 := H1 H2 : False in False_ind False H3)
: forall P Q : Prop, ~ (P \/ Q) -> ~ P /\ ~ Q
>>
That's a bit more complicated of a proof than the one we constructed ourselves,
mainly because of the [let] expressions, but it still is correct.
So if your CS 2800 prof wants you to prove a propositional tautology, and if
it's a proposition that holds in constructive logic, Coq's got your back: just
use [tauto] to do it.
But don't tell your 2800 prof I told you that.
** Summary
Coq's built-in logic is constructive: it isn't sufficient to argue that
a proposition must be true or false; rather, we have to construct evidence
for the proposition. Programs are how we construct and transform evidence.
All of the propositional connectives, except implication, are "coded up"
in Coq using inductive types, and proofs about them routinely use pattern
matching and function application.
** Terms and concepts
- assumption
- classical logic
- conjunction
- constructive logic
- constructor
- contradiction
- De Morgan's laws
- disjunction
- double negation
- evidence
- excluded middle
- implication
- inductive type
- negation
- Principle of Explosion
- [Prop]
- proposition
- reflexivity
- [Set]
- syllogism
- tautology
- transitivity
- truth table
** Tactics
- [apply]
- [assumption]
- [contradiction]
- [destruct..as]
- [exact]
- [left]
- [right]
- [split]
- [tauto]
- tacticals: nested bullets [-], [*], [+]
** Further reading
- _Software Foundations, Volume 1: Logical Foundations_.
#
Chapter 6: Logic.#
- _Interactive Theorem Proving and Program Development_.
Chapters 5 and 8.2. Available
#
online from the Cornell library#.
*)