Curry-Howard: Logic as Computation
ToDo: More explanation.
Empty ↦ False
Empty
You've already met and understood the Empty data type.
EmptyEmpty: Type
inductive Empty : Type
As an example, here's another uninhabited type (in Type)
inductive Chaos: Type
Chaos : Type: Type 1
Type
From an assumption that one has a value of type Empty, anything can follow. We can even promise to return a value of our new uninhabited type.
def from_empty: Empty → Chaos
from_empty (e: Empty
e : Empty: Type
Empty) : Chaos: Type
Chaos := nomatch e: Empty
e
False
The logical analog of the Empty data type is the proposition, False. It is an uninhabited type, but now in Prop. Such a type is understood as representing a proposition. That there is no proof of False---no value of this type---means that as a proposition it is logically false.
FalseFalse: Prop
inductive False : Prop
def from_false: ∀ {P : Prop}, False → P
from_false {P: Prop
P : Prop: Type
Prop} (p: False
p : False: Prop
False) : P: Prop
P := False.elim: ∀ {C : Prop}, False → C
False.elim p: False
p
def from_false_true_is_false: False → True = False
from_false_true_is_false (p: False
p : False: Prop
False) : True: Prop
True = False: Prop
False := False.elim: ∀ {C : Prop}, False → C
False.elim p: False
p
-- no introduction rule, as there are no proofs of False
Unit ↦ True
Unit
Unit -- inductive PUnit : Sort u where -- | unit : PUnitUnit: Type
True
TrueTrue: Prop
inductive True : Prop where | intro : True
True.intro -- no elimination rule defTrue.intro: Trueproof_of_true :proof_of_true: TrueTrue :=True: PropTrue.introTrue.intro: True
Example
def false_implies_true: False → Chaos
false_implies_true : False: Prop
False → Chaos: Type
Chaos :=
λ f: False
f => False.elim: {C : Type} → False → C
False.elim f: False
f
Prod ↦ And
Prod
Prod /- structure Prod (α : Type u) (β : Type v) where fst : α snd : β -/Prod: Type u → Type v → Type (max u v)
And
And /- structure And (a b : Prop) : Prop where intro :: left : a right : b -/ -- Propositions as types, proofs as values inductiveAnd: Prop → Prop → PropBirds_chirping :Birds_chirping: PropProp |Prop: Typeyep |yep: Birds_chirpingboo -- Propositions as types, proofs as values inductiveboo: Birds_chirpingSky_blue :Sky_blue: PropProp |Prop: Typeyepyep: Sky_blue(AndAnd: Prop → Prop → PropBirds_chirpingBirds_chirping: PropSky_blue)Sky_blue: Prop(Birds_chirping ∧Birds_chirping: PropSky_blue) theoremSky_blue: Propa_proof :a_proof: Birds_chirping ∧ Sky_blueBirds_chirping ∧Birds_chirping: PropSky_blue := -- And.intro Birds_chirping.yep Sky_blue.yep ⟨Sky_blue: PropBirds_chirping.yep,Birds_chirping.yep: Birds_chirpingSky_blue.yep ⟩ -- notationSky_blue.yep: Sky_blue
On Proof Irrelevance
For the purpose of demonstrating that a given proposition is true (or, more accurately, valid), any proof will do. All proofs are equivalent in this dimension. In Prop, all proof values are considered to be equal. Moreover, choices among otherwise equivalent proofs aren't allowed to affect rsults of computations.
namespace cs2120f23
With values of data types, we care a lot about particular values. There's a huge difference between tre and false as values of the Boolean type,
Indeed, one of the fundamental rules of inductive data type definitions (in Type or above) is that constructors are disjoint. This means that different constructors always create values that are different: unequal.
inductive Bool: Type
Bool : Type: Type 1
Type
| true: Bool
true
| false: Bool
false
But because Birds_chirping is in Prop (it's a proposition, right) all of its values, all values accepted as proofs of the propisition, are actually considered to be equal. You would understand the details of the formal proof until we talk about equality, but you can trust that Lean is accepting that there is a proof that boo and yep really are equal.
theoremproof_equal :proof_equal: Birds_chirping.boo = Birds_chirping.yepBirds_chirping.boo =Birds_chirping.boo: Birds_chirpingBirds_chirping.yep :=Birds_chirping.yep: Birds_chirpingGoals accomplished! 🐙Goals accomplished! 🐙
Major take-away: values of propositional types are not just all equally acceptable as mathematical proof objects, they are considered as all being literally equal. We will talk about equality in more detail soon.
Sum ↦ Or
Sum Data Type
Sum /- inductive Sum (α : Type u) (β : Type v) where | inl (val : α) : Sum α β | inr (val : β) : Sum α β -/Sum: Type u → Type v → Type (max u v)
Or Connective
Or /- inductive Or (a b : Prop) : Prop where | inl (h : a) : Or a b | inr (h : b) : Or a b -/ -- Two different proofs of the same proposition, theoremOr: Prop → Prop → Propone_or_other :one_or_other: Birds_chirping ∨ Sky_blueOrOr: Prop → Prop → PropBirds_chirpingBirds_chirping: PropSky_blue :=Sky_blue: PropOr.inlOr.inl: ∀ {a b : Prop}, a → a ∨ bBirds_chirping.yep theoremBirds_chirping.yep: Birds_chirpingone_or_other' :one_or_other': Birds_chirping ∨ Sky_blueOrOr: Prop → Prop → PropBirds_chirpingBirds_chirping: PropSky_blue :=Sky_blue: PropOr.inrOr.inr: ∀ {a b : Prop}, b → a ∨ bSky_blue.yepSky_blue.yep: Sky_blueexample :example: one_or_other = one_or_other'one_or_other =one_or_other: Birds_chirping ∨ Sky_blueone_or_other' :=one_or_other': Birds_chirping ∨ Sky_bluerfl -- the different proofs are equalrfl: ∀ {α : Prop} {a : α}, a = a
In some cases you'll need to select the disjuct for which you have a proof.
example :example: Birds_chirping ∨ 0 = 1OrOr: Prop → Prop → PropBirds_chirping (Birds_chirping: Prop0=0: Nat1) :=1: NatOr.inlOr.inl: ∀ {a b : Prop}, a → a ∨ bBirds_chirping.yep -- inr is no goBirds_chirping.yep: Birds_chirpingexample : (example: 0 = 1 ∨ 1 = 20=0: Nat1) ∨ (1: Nat1=1: Nat2):=2: Nat-- no proof of either disjunct: false
We know have ample machinery to prove interesting theorems in fundamental mathematical logic. As an example, we now state and construct a proof of the proposition that Or is commutative. Technically it's a biimplication, but the cases are symmetric so we'll just consider one direction: if we assume that P and Q are arbitrary propositions, then if the proposition P ∨ Q is true (and we have a proof, poq) then we can derive a proof of Q ∨ P, showing that it is true, and that our overall proposition is valid. The proof of that is by case analysis
theorem or_comm {P Q : Prop} : P ∨ Q → Q ∨ P :=
λ (poq : P ∨ Q) =>
match poq with
| Or.inl p => Or.inr p
| Or.inr q => Or.inl q
Negation
no
When representing logical operations using computational types (in Type), we represented the proof of a negation of a proposition, α, as a function (implementation) from (of type) α → Empty.
If there is a function of this type, then the type, α, must be uninhabited: there are no proofs of it; so it is false and the proposition ¬α is true. To prove ¬α, give a function of type α → Empty. This is all of course when implementing logical reasoning in using computational (Sum, Prod, Empty, and other) types (in Type).
def no (α : Type) : Type := α → Empty
Example. Recall we defined Chaos as uninhabited in Type. This is how we model the notion that Choas is false. We no Chaos literally means (the proposition-representing function type) Chaos → Empty. What we do in this example is to show that there is a value of this function type. The function takes a Chaos value (proof) as an argument., for which an empty case analysis satisfies the obligation of the function to return a value in each case.
example : no Chaos := λ c => nomatch c
Not(¬)
We'll now see that the approach is analogous in Prop, the type Universe for logical reasoning in Lean. If P is any proposition, then Not P (concrete notation, ¬P) is also a proposition. It is true when P (the type of proofs of P) is uninhabited.
NotNot: Prop → Prop
def Not (a : Prop) : Prop := a → False
Compare this directly and carefully with how we defined the corresponding concept, no, using computational types and values (in or under Type).
-- Computational
example: no Chaos
example : no: Type → Type
no Chaos: Type
Chaos := λ (c: Chaos
c : Chaos: Type
Chaos) => nomatch c: Chaos
c
-- Logical
-- A proposition with no proofs is false
-- Here then is a proposition, it's raining, that's false
inductive Raining: Prop
Raining : Prop: Type
Prop
-- What's cool is that we can now prove it's negation (is valid)
example: ¬Raining
example : ¬Raining: Prop
Raining := λ (r: Raining
r : Raining: Prop
Raining) => nomatch r: Raining
r
-- Compare this with a corresponding example in Type