Library fin_em2
Today I try to formalize the basic intuition that excluded middle is
constructively true when we're working with finite types. I will prove
a "brute force" theorem which says that we can determine the truth
of a predicate by testing it over all values.
Through proofs-as-programs (aka Curry-Howard aka BHK corresponedence
aka many other things), this is also a brute force program! This is
why I use Defined instead of Qed and sig (subset types) instead of ∃.
It really does work, as I will demonstrate after proving the
theorem. Actually, the first version of the proof had a curious
peculiarity: even after finding a counterexample, it would keep going
and try to find the largest counterexample. If you move around the
destruct in the theorem statement for em, you can recreate this
maximum-counterexample program! Just one fun way in which choice of
proof can become "relevant".
I eventually prove the theorem for a representative finite set type,
and then show that it extends to any type isomorphic to a finite set type.
This is used to give the theorem for Fin.t, which is used e.g.
in Vector and thus somewhat unavoidable.
Require Import Setoid Program.
Require Import Arith Utf8 CpdtTactics Decidable Compare_dec List Bool.
Require Fin.
Set Implicit Arguments.
Fixpoint factorial n :=
match n with
| 0 => 1
| (S pn) => n * factorial pn
end.
Print factorial.
Section em.
Hypothesis P : nat → Prop.
Lemma forall_extends:
∀ n, (∀ x : nat, x < n → P x)
→ P n
→ (∀ x : nat, x < S n → P x).
intros ? ? ? ? x_lt_S_n; inversion x_lt_S_n; crush.
Defined.
Hint Resolve forall_extends.
Lemma sig_extends:
∀ n (base : {x : nat | x < n ∧ (P x → False)}),
{x : nat | x < S n ∧ (P x → False)}.
intros; destruct base as (?,(?,?)); eauto.
Defined.
Hint Resolve sig_extends.
Since we want an efficient algorithm, we are a little
careful in this proof: we make sure we only compute (d x)
if we have to, so we destruct the induction hypothesis
earlier to see if we already have a counterexample.
Theorem em :
∀ n (d : ∀ x, x < n → (P x) + ~ (P x)),
(∀ x, x < n → P x) + {x | x < n ∧ ~ P x}.
intros.
induction n. intuition.
assert (H : (∀ x : nat, x < n → P x) + {x | x < n ∧ (P x → False)}) by eauto.
destruct H; eauto.
destruct (d n); intuition eauto.
Defined.
Hint Resolve em.
End em.
Extraction em.
∀ n (d : ∀ x, x < n → (P x) + ~ (P x)),
(∀ x, x < n → P x) + {x | x < n ∧ ~ P x}.
intros.
induction n. intuition.
assert (H : (∀ x : nat, x < n → P x) + {x | x < n ∧ (P x → False)}) by eauto.
destruct H; eauto.
destruct (d n); intuition eauto.
Defined.
Hint Resolve em.
End em.
Extraction em.
Let's demonstrate the power of our program!
Definition P1 x := x ≠ 2.
Hint Unfold P1.
Lemma d_P1 : ∀ n x, x < n → (P1 x) + ~ (P1 x).
intros.
pose proof (eq_nat_dec x 2).
intuition.
Defined.
Eval vm_compute in (em P1 (d_P1 (n := 5))).
Hint Unfold P1.
Lemma d_P1 : ∀ n x, x < n → (P1 x) + ~ (P1 x).
intros.
pose proof (eq_nat_dec x 2).
intuition.
Defined.
Eval vm_compute in (em P1 (d_P1 (n := 5))).
Okay that was not very exciting. I found some better facts here:
http://www2.stetson.edu/~efriedma/numbers.html .
On this page it says that n=863 gives n(n + 6) a palindrome.
Are there medium-size (>1) numbers that also satisfy this constraint?
Let's find out!
First we must quickly hack up a way to represent decimal numbers,
since we really meant a base-10 palindrome. We represent e.g.
123 as 3,2,1.
Definition number_stream_10 := list nat.
Fixpoint inc (n : number_stream_10) :=
match n with
| [] => 1 :: []
| cons digit ds =>
match lt_dec digit 9 with
| left _ => (digit + 1) :: ds
| right _ => 0 :: (inc ds)
end
end.
Fixpoint to_number_stream_10 x :=
match x with
| 0 => []
| S px => inc (to_number_stream_10 px)
end.
To test what we have so far: Compute (to_number_stream_10 12).
It gave me 2; 1, so I probably haven't messed up yet.
Lemma dec_eq10 (a b : number_stream_10) : {a = b} + {a ≠ b}.
refine (list_eq_dec _ a b).
intros.
pose proof (eq_nat_dec x y).
intuition.
Defined.
Fixpoint palindrome' (front rear : number_stream_10): bool :=
match front with
| [] => if dec_eq10 rear (nil (A := nat))
then true else false
| b :: bs =>
(if dec_eq10 bs rear then true else false) || (palindrome' bs (b :: rear))
end.
To test what we have so far, I tried:
Compute (palindrome' (to_number_stream_10 333)) ☐.
It also really did work. Now we can define what we were really
interested in: this is the predicate P2.
Definition palindrome n := palindrome' (to_number_stream_10 n) [] = true.
Definition P2 n := ¬ (palindrome (n * (n + 6)) ∧ n ≠ 0 ∧ n ≠ 1).
Lemma d_palindrome : ∀ n x, x < n → (P2 x + ~ P2 x).
intros.
unfold P2.
unfold palindrome.
destruct (eq_nat_dec x 1). intuition.
destruct (eq_nat_dec x 0). intuition.
destruct (palindrome' (to_number_stream_10 (x * (x + 6))) []); intuition.
Defined.
If you look at the output of this, you find 22 (the proof
term is actually quite small and reasonable). It really does
work! Note using vm_compute is
definitely the way to go here, it is much faster than others!
Eval vm_compute in (em P2 (d_palindrome (n := 1000))).
Extraction em.
Definition Finite n := {x : nat | x < n}.
Hint Unfold Finite.
Definition finite x n (prf : x < n) : Finite n
:= exist (λ y : nat, y < n) x prf.
Local propositional irrelevance. By using dependent destruction,
we bring in Axiom K and JMeq -> normal equality (Print Assumptions
irr). Just adopting proof irrelevance would have worked fine too.
Lemma irr : ∀ m n (p1 p2 : m < n), p1 = p2.
induction n; intuition.
dependent destruction p1; dependent destruction p2; intuition.
assert (p1 = p2); crush.
Qed.
Hint Rewrite irr.
Hint Resolve irr.
This tactic uses irrelevance to identify all terms
of the same (a < b) type. This actually would be unecessary
if the lemmas used computed, but they are marked by Qed.
Ltac auto_irr :=
match goal with
| [ |- context[?a]] =>
match (type of a) with
| (_ < _) =>
match goal with
| [ |- context[?b]] =>
match (type of b) with
| (_ < _) =>
rewrite (irr a b)
end
end
end
end; try trivial.
Section finite_em.
Hypothesis n : nat.
Hypothesis P : Finite n → Prop.
Hypothesis d : ∀ x, (P x) + (~ P x).
match goal with
| [ |- context[?a]] =>
match (type of a) with
| (_ < _) =>
match goal with
| [ |- context[?b]] =>
match (type of b) with
| (_ < _) =>
rewrite (irr a b)
end
end
end
end; try trivial.
Section finite_em.
Hypothesis n : nat.
Hypothesis P : Finite n → Prop.
Hypothesis d : ∀ x, (P x) + (~ P x).
We need to craft these predicates into the form that em wants them,
starting with P.
Definition P' x :=
match lt_dec x n with
| left is_lt => P (finite is_lt)
| right _ => False
end.
Ltac simplP' :=
match goal with
| |- context[P' ?y] =>
unfold P';
destruct (lt_dec y _)
| [ H : context[P' ?y] |- _ ] =>
unfold P' in H;
destruct (lt_dec y _)
end.
Ltac hyp_irr :=
match goal with
| [ lt1 : ?a < ?b, lt2 : ?a < ?b |- _ ] =>
cut (lt1 = lt2); crush
end.
Lemma P_to_P' : ∀ x, P x → P' (proj1_sig x).
intros.
destruct x; simplP'; solve [auto] || hyp_irr.
Defined.
Hint Resolve P_to_P'.
Lemma P'_to_P : ∀ (x : Finite n), P' (proj1_sig x) → P x.
intros.
destruct x; simplP'; solve [crush] || hyp_irr.
Defined.
Hint Resolve P'_to_P.
Lemma d' (x : nat) (is_lt : x < n): (P' x) + (~ P' x).
simplP'; crush.
Defined.
Lemma finite_1 :
(∀ x : nat, x < n → P' x) → (∀ x : Finite n, P x).
destruct x; auto.
Defined.
Hint Resolve finite_1.
Lemma finite_2 : {x | x < n ∧ ~P' x} → {y | ¬P y}.
intro ex; destruct ex as (x,(x_lt_n, not_P'_x)); intuition.
exists (finite x_lt_n).
intro P_y; exact (not_P'_x (P_to_P' P_y)).
Defined.
Hint Resolve finite_2.
Theorem finite_em : (∀ x, P x) + {x | ~ P x}.
destruct (em P' d'); crush; eauto.
Defined.
Hint Resolve finite_em.
Hint Immediate finite_em.
End finite_em.
Section generalized_em.
Hypothesis T : Type.
Hypothesis P : T → Prop.
Hypothesis n : nat.
Hypothesis intoFinite : T → Finite n.
Hypothesis intoT : Finite n → T.
Hypothesis T_id : ∀ x, (intoFinite ∘ intoT) x = x.
Hypothesis Finite_id : ∀ x, (intoT ∘ intoFinite) x = x.
Hypothesis d : ∀ x, (P x) + (~ P x).
Definition PF x := P (intoT x).
Hint Unfold PF.
Lemma dF : ∀ x, (PF x) + (~ PF x).
eauto.
Defined.
Hint Resolve dF.
Lemma pF : (∀ x, PF x) + {x | ~ PF x}.
apply finite_em.
eauto.
Defined.
Hint Resolve pF.
Lemma generalized_em : (∀ x, P x) + {x | ~ P x}.
destruct pF; unfold PF in *.
- left. intro.
rewrite <- Finite_id.
eauto.
- right.
destruct s.
eauto.
Defined.
End generalized_em.
Definition FiniteFromFin (n : nat) (t : Fin.t n): Finite n :=
Fin.to_nat t.
Hint Unfold FiniteFromFin.
Definition FinFromFinite (n : nat) (t : Finite n) :=
match t with
| exist x xltn => Fin.of_nat_lt xltn
end.
Hint Unfold FinFromFinite.
Lemma Fin_id : ∀ n (x : Fin.t n), FinFromFinite (FiniteFromFin x) = x.
intros.
induction x; trivial.
unfold FiniteFromFin in *.
unfold FinFromFinite in *.
unfold Fin.to_nat.
fold @Fin.to_nat.
destruct (Fin.to_nat x).
crush.
auto_irr.
Defined.
Lemma Finite_id: ∀ n (x : Finite n), FiniteFromFin (FinFromFinite x) = x.
induction n.
- destruct x. inversion l.
- destruct x; destruct x; simpl.
+ auto_irr.
+ fold (FinFromFinite (exist _ x (lt_S_n x n l))).
rewrite IHn.
auto_irr.
Defined.
やった!