Using Evidence in Proofs
In addition to constructing evidence that numbers are even, we can also reason about such evidence. Introducing Ev with a data statement tells Kind not only that the constructors ev_z
and ev_ss
are valid ways to construct evidence that a number is even, but also that these two constructors are the only ways to construct evidence that numbers are even (in the sense of Ev).
In other words, if someone gives us evidence and claims it to be of the form Ev n
, then we know that e must have one of two forms:
e
isev_z
(and n is Nat.zero), ore
isev_ss
applied to induction withn
and it is equal to the successor of the successor ofn
*.
We've already used this strategy before, remember the Problems.t3 exercise from the induction chapter, here the difference is that there's only one more "Nat.succ" in our induction
This suggests that it should be possible to analyze a hypothesis of the form Ev n
in the same way we do with data structures defined inductively; in particular, it should be possible to argue by induction and case analysis on this evidence. Let's see some examples to understand what this means in practice.
Pattern Matching on Evidence
Suppose we are proving some fact involving a number n and we are given the hypothesis Ev n
. We already know how to perform case analysis on n
using the inversion tactic, generating separate subgoals for the case where n = Nat.zero
and the case where n = Nat.succ n
for some n
. But for some proofs, we may want to directly analyze the evidence that Ev n
is true.
By the definition of Ev, there are two cases to consider:
- If the evidence is of the form
ev_z
, we know thatn = Nat.zero
. - Otherwise, the evidence must have the form
ev_ss n e
, wheren = Nat.succ (Nat.succ n)
ande
is the evidence forEv n
.
We can reason about this kind of pattern matching in Kind, again using pattern matching. In addition to allowing us to reason about equalities involving constructors, inversion provides a principle of case analysis for propositions defined inductively.
Ev_minus2 (n: Nat) (e: Ev n) : Ev (Nat.pred (Nat.pred n))
Ev_minus2 Nat.zero e = e
Ev_minus2 (Nat.succ Nat.zero) e = Ev.ev_z
Ev_minus2 (Nat.succ (Nat.succ n)) (Ev.ev_ss e) = e
In words, here is how the pattern matching reasoning works in this proof:
- If the evidence is of the form
ev_z
, we know thatn = Nat.zero
. Therefore, it is sufficient to show thatEv (Nat.pred (Nat.pred Nat.zero))
is valid. By the definition ofNat.pred
, this is equivalent to showing thatEv Z
is valid, which follows directly fromev_0
. - Otherwise, the evidence must have the form
ev_ss n e
, wheren = Nat.succ (Nat.succ n)
ande
is the evidence forEv n
. We must then show thatEv (Nat.pred (Nat.pred (Nat.succ (Nat.succ n))))
is valid, which, after simplification, follows directly frome
.
Evss_ev (n: Nat) (e: Ev (Nat.succ (Nat.succ n))) : Ev n
Intuitively, we know that the evidence for the hypothesis cannot consist solely of the constructor ev_z
, since Nat.zero
and Nat.succ
are different constructors of the type Nat; therefore, ev_ss
is the only applicable case. Unfortunately, pattern matching is not smart enough to realize this and still generates two subgoals. Even worse, by doing so, it leaves the final goal unchanged, failing to provide any useful information to complete the proof.
The inversion tactic, on the other hand, can detect (1) that the first case does not apply and (2) that the n appearing in the Ev_SS case must be the same as n. This allows us to conclude the proof.
Evss_ev n (Ev.ev_ss e) = e
Using dependent pattern matching, we can also apply the principle of explosion to "obviously contradictory" hypotheses involving inductive properties. For example:
One_not_even : Not (Ev 1n)
Inversion_practice
Prove the following results using pattern matching.
Ssssev__even (n: Data.Nat) (e: Ev (Data.Nat.succ (Data.Nat.succ (Data.Nat.succ (Data.Nat.succ n))))) : Ev n
Ssssev__even n e = ?
Even5_nonsense (e: Ev 5n) : Prop.Equal (Data.Nat.add 2n 2n) 9n
Even5_nonsense e = ?
The way we use inversion here may seem a bit mysterious at first. So far, we have only used inversion on equality propositions to make use of the injectivity of constructors or to discriminate between different constructors. But we see here that inversion can also be applied to analyze evidence of inductively defined propositions.
Here is how inversion works in general. Suppose the name I refers to an assumption P in the current context, where P was defined by an Inductive statement. Then, for each of the constructors of P, inversion of I generates a subgoal in which I has been replaced by the exact and specific conditions under which this constructor could have been used to prove P. Some of these subgoals will be self-contradictory; inversion discards those. The ones that are left represent the cases that must be proven to establish the original goal. For these, inversion adds all the equations to the proof context that must hold true for the arguments supplied to P (e.g., Nat.succ (Nat.succ k) = n
in the proof of evSS_ev).
The exercise ev_double above shows that our new notion of evenness is implied by the two previous ones (since, by even_bool_prop in the Logic chapter, we already know they are equivalent to each other). To show that the three coincide, we just need the following lemma:
Ev_even
(n: Nat)
(e: Ev n) :
(Sigma Nat (k => Prop.Equal n (Nat.double k)))
Ev_even n e = ?
We proceed by case analysis on Ev n. The first case can be resolved trivially.
Ev_even Nat.zero e = Sigma.new 0n Equal.refl
Unfortunately, the second case is more challenging. We need to show [k: Nat] -> (Equal (Nat.succ (Nat.succ n')) (Nat.double k))
, but the only assumption available is e'
, which states that Ev n'
is true. Since this is not directly useful, it seems like we are stuck and that the case analysis on Ev n
was a waste of time.
Ev_even (Nat.succ Nat.zero) e = Empty.absurd _ //todo
If we take a closer look at our second goal, however, we can see that something interesting happened: by performing case analysis on Ev n
, we were able to reduce the original result to a similar one that involves a different evidence for Ev n: e'
. More formally, we can conclude our proof by showing that
Ev_even (Nat.succ (Nat.succ n)) (Ev.ev_ss e) = Ev_even_ss n (Ev_even n e)
which is the same as the original statement, but with n'
instead of n
. In fact, it is not difficult to convince Kind that this intermediate result is sufficient.
Ev_even (Nat.succ (Nat.succ n)) (Ev.ev_ss e) = Ev_even_ss n (Ev_even n e)
Induction on Evidence
If this seems familiar, it's no coincidence: we encountered similar problems in the Induction chapter when trying to use case analysis to prove results that required induction. And once again, the solution is... induction!
Let's try our current lemma again:
Ev_even
(n: Nat)
(e: Ev n) :
(Sigma Nat(k => Equal n ( Nat.double k)))
Ev_even Nat.zero e = Sigma.new 0n Equal.refl
Ev_even (Nat.succ Nat.zero) e = Empty.absurd _
Ev_even (Nat.succ (Nat.succ n)) (Ev.ev_ss e) = Ev_even_ss n (Ev_even n e)
// Ev_even (Nat.succ (Nat.succ n)) Ev.ev_z = Caso impossível
The equivalence between the second and third definitions of evenness now follows.
Ev_even_equiv (n: Nat) : Equivalence (Ev n) (Sigma Nat (k => Equal n (Nat.double k)))
Ev_even_equiv n = Equivalence.new (x => Ev_even n x) (y => From_eee n y)
From_eee (n: Nat) (s: Sigma Nat (k => Equal n (Nat.double k))) : Ev n
From_eee n (Sigma.new a b fst snd) =
Equal.rewrite (Equal.mirror (specialize b into #0 in snd)) (x =>(Ev x)) (Ev_double fst)
Ev_double (n: Nat) : Ev (Nat.double n)
Ev_double Nat.zero = Ev.ev_z
Ev_double (Nat.succ n) = Ev.ev_ss (Ev_double n)
As we will see in the upcoming chapters, induction on evidence is a recurring technique in various areas, especially in the formalization of programming language semantics, where many properties of interest are defined inductively.
The following exercises provide simple examples of this technique to help you become familiar with it.
Ev_sum
Ev_sum (n: Nat) (m: Nat) (e1: Ev n) (e2: Ev m) : Ev (Nat.add n m)
Ev_sum n m e1 e2 = ?
Ev_alternate
In general, there can be multiple ways to define a property inductively. For example, here is an alternative (somewhat forced) definition for Ev:
type Evn ~ (n: Nat){
z : Evn Nat.zero
d : Evn (Nat.succ (Nat.succ Nat.zero))
sum <n : Nat> <m: Nat> (evn: Evn n) (evm: Evn m) : Evn (Nat.add n m)
}
Prove that this definition is logically equivalent to the old one. (You may want to refer to the previous theorem when you reach the induction step.)
Ev_evn (n: Nat): Equivalence (Ev n) (Evn n)
Ev_evn n = ?
Ev_ev__ev
Finding the appropriate thing to induct on is a bit tricky here:
Ev_ev_ev (n: Nat) (m: Nat) (e: Ev (Nat.add n m)) (en: Ev n) : Ev m
Ev_ev_ev Nat.zero m e en = ?
Ev_plus_plus
This exercise only requires the application of existing lemmas. No induction or even case analysis is needed, although some of the rewrites may be tedious.
Ev_pp
<n: Nat>
<m: Nat>
<p: Nat>
(e1: Ev (Nat.add n m))
(e2: Ev (Nat.add n p))
: Ev (Nat.add m p)
Ev_pp Nat.zero m p e1 e2 =
``