lecture3Lecture III: Double negation
"From any constructive proof we can extract a program."
Theorem: If HA ⊢ φ, then ⊩ φ.
Proof (part):
─────────── A realizer for (α ⇒ (α ∨ β))
α ⊢ α ∨ β looks like this:
# input: a realizer for α
# output: a realizer for α ∨ β
def e(x):
return (0, x)
What is the problem with extracting programs from
classical proofs? Why don't we have
if PA ⊢ φ, then ⊩ φ?
For this, the law of excluded middle (LEM) would
need to have a realizer. How would such a realizer look like?
# input: a proposition A
# output: either (0,x) where x is a realizer for A
# or (1,y) where y is a realizer for (A ⇒ ⊥)
def oracle(A):
return (1, lambda x: outer-return (0,x))
# ^^^^^^^^^^^^^^^ a realizer for (A ⇒ ⊥)
# input: a realizer for A
# output: a realizer for ⊥
There is no realizer for LEM in ordinary programming languages.
But there IS a realizer for LEM in languages with support for backtracking.
Theorem: If PA ⊢ φ, then ⊩' φ.
"From every classical proof, we can extract a program (which might backtrack)."
Let's consider the following statement:
∀f : ℕ → ℕ. ∃a : ℕ. ∀b : ℕ. f(a) ≤ f(b).
"Every function f : ℕ → ℕ attains a minimum (namely f(a))".
This statement doesn't have a constructive proof and there is no realizer in
the ordinary sense. But allowing backtracking, a realizer looks like this:
# input: a function f : ℕ → ℕ
# output: a pair (a,p) where a : ℕ
# and p is a function which inputs a number b : ℕ
# and outputs a realizer
# for f(a) ≤ f(b).
def minimum(f):
def go(a):
return (a, lambda b: if f(a) ≤ f(b) then * else outer-return go(b))
go(0)
Observe: While A ∨ ¬A is not constructively provable, its close cousin
¬¬ (A ∨ ¬A)
is! Here is a constructive proof:
Claim: ((A ∨ ¬A) ⇒ ⊥) ⇒ ⊥.
Proof: Assume ((A ∨ ¬A) ⇒ ⊥), we need to verify ⊥.
Subclaim: ¬A.
Subproof: Assume A. Then in particular A ∨ ¬A. By assumption, ⊥.
As ¬A holds, in particular we have A ∨ ¬A. By assumption, ⊥.
Idea: Define ∨ᶜˡ, ∃ᶜˡ, =ᶜˡ:
α ∨ᶜˡ β := ¬ ¬ (α ∨ β)
∃ᶜˡ x. φ(x) := ¬ ¬ (∃x. φ(x))
x =ᶜˡ y := ¬ ¬ (x = y)
Observe: For any A, A ∨ᶜˡ (¬ A).
For a formula φ, its double negation translation φ* is obtained by replacing
any ∨ by ∨ᶜˡ, any ∃ by ∃ᶜˡ, and any = by =ᶜˡ. For instance
(∀f : ℕ → ℕ. ∃a : ℕ. ∀b : ℕ. f(a) ≤ f(b))* is
∀f : ℕ → ℕ. ¬¬∃a : ℕ. ∀b : ℕ. ¬¬ f(a) ≤ f(b).
Note that we can "reason under double negation": It is a constructive tautology that
(¬¬A ∧ (A ⇒ B)) ⇒ ¬¬B.
Even more, we have:
(¬¬A ∧ (A ⇒ ¬¬B)) ⇒ ¬¬B.
[ this is, in fact, the "bind operation" of a certain monad ]
More precisely, we have the following metatheorem:
(0) Constructively, ¬¬⊥ ⇒ ⊥. (Never forget: ¬P :≡ (P ⇒ ⊥))
(1) Classically, φ* ⇔ φ.
(2) Constructively, ¬¬(φ*) ⇒ φ*. (This makes use of ¬¬¬¬α ⇒ ¬¬α)
(3) Constructively, φ* ⇔ ¬¬φ --- IF φ is a "coherent formula"
(a formula in which only = ⊤ ⊥ ∧ ∨ ∃, but no ⇒ ∀ occur).
(4) Classical logic proves α ⊢ β (from some set Γ of axioms) if and only if
constructive logic proves α* ⊢ β* (from the set Γ*, where Γ* = { γ* | γ ∈ Γ }).
Note: With the exceptio of (1), Theorems (0) to (4) work just as well for any
proposition F in place of ⊥. In this case the negations appearing in the
formulation of these theorems (and also in the definition of the double
negation translation) mean implication to F instead of implication to the
original ⊥.
What does a realizer for a statement of the form "∀x:ℕ. P(x)" look like?
// input: a number x ∈ ℕ
// output: a realizer for P(x)
function e(x) {
.........compute a realizer r for P(x).......
return r;
}
What does a realizer for a statement of the form "∀x:ℕ. ¬¬P(x)" look like?
// input: a number x ∈ ℕ
// output: a realizer for ¬¬P(x) = ((P(x) ⇒ ⊥) ⇒ ⊥)
function e(x) {
return(
// input: a realizer for P(x) ⇒ ⊥
// output: a realizer for ⊥
function (callback) {
.........compute a realizer r for P(x).......
// NOTE: this computation↑↑↑ is allowed to make use of the callback!
return callback(r);
}
);
}
// That would be nice, but we don't have that:
let contents = fetch("https://wikipedia.org/...");
let foo = bar(contents);
let abc = baz(foo);
// Instead, the API is the following:
fetch("https://wikipedia.org/...", function (contents) {
bar(contents, function (foo) {
baz(foo, function (abc) {
...;
});
});
});
...;
...;
...;
Here is a realizer for "∀A. ¬¬(A ∨ ¬A)":
# input: a proposition A
# output: a realizer for ((A ∨ ¬A) ⇒ ⊥) ⇒ ⊥
def oracle(A):
return lambda callback: callback((1, lambda x: callback((0,x))))
# input: a realizer for (A ∨ ¬A) ⇒ ⊥
# output: a realizer for ⊥
Note: The CPS transformation in computer science is the same as
the double negation translation in logic.
More precisely, the CPS transformation is the shadow cast on programs
from the double negation translation.
Theorem:
(5) If classical logic proves α ⊢ β (from some set Γ of axioms),
then also constructive logic proves α ⊢ β (from the same set Γ of axioms)
---assuming that α ⊢ β and all the axioms in Γ are coherent sequents
(that is, their antecedents and consequents are all coherent formulas;
free variables are allowed).
Proof: Choosing F :≡ β, we have, constructively,
(3) (3)
α ⊢ ¬¬α ⊢ α* ⊢ β* ⊢ ¬¬β ≡ ((β ⇒ F) ⇒ F) ⊢ β.
((β ⇒ β) ⇒ β) ⊢ β.
This theorem has various names: Friedman's trick, Barr's theorem, escaping the
continuation monad, ...
Moral: If at the end of the day all we want is to prove a coherent sequent
(from a set of coherent sequents as axioms), then we can regard the law of
excluded middle as a useful fiction. Not actually available in constructive
mathematics, but we can often rightfully pretend that it is.
This holds even if auxiliary results required for the proof fail the coherence
requirement and cannot individually be constructivized using Theorem (5)!
Example: The statement that every function ℕ → ℕ attains a minimum cannot
individually be constructivized. But we can nevertheless use it as a tool in a
constructive proof of Dickson's Lemma!