/-! # Propositions and Proofs As we have mentioned, proofs and propositions are terms, with propositions as types. The universe `Prop` is the type of propositions. To start with, it is best to ignore the difference between `Prop` and `Type` and treat them as the same. -/ /-! A logical statement like `1 ≤ 2` is a proposition and has type `Prop`. -/ #check 1 ≤ 2 -- Prop #check Prop -- Type #check Type -- Type 1 #check Nat.zero_le -- Nat → 0 ≤ n #check Nat.le_refl -- ∀ (n : Nat), n /-! The proposition `0 ≤ 2` is true and has proof `Nat.zero_le 2`. -/ #check Nat.zero_le 2 -- 0 ≤ 2 #check Nat.le -- Nat → Nat → Prop /-! A proposition may be neither proved nor disproved. So we cannot evaluate it. We can evaluate it if it is decidable. -/ /-- error: failed to synthesize Decidable (Nat.le 1 2) Additional diagnostic information may be available using the `set_option diagnostics true` command. -/ #guard_msgs in #eval Nat.le 1 2 #check False -- Prop #check True -- Prop /-! We can combine propositions to make propositions: * If `P` and `Q` are propositions, then `P ∧ Q` is the proposition "P and Q". * If `P` and `Q` are propositions, then `P ∨ Q` is the proposition "P or Q". * If `P` and `Q` are propositions, then `P → Q` is the proposition "if P then Q". * If `P` is a proposition, then `¬ P` is the proposition "not P", which is `P → False`. The key idea is that function application is exactly analogous to the logical rule of inference called modus ponens. -/ def modus_ponens {P Q: Prop} (h₁ : P) (h₂ : P → Q) : Q := h₂ h₁ def application {α β : Type} (a: α) (f: α → β) : β := f a #check Nat.succ -- Nat → Nat #eval application 1 Nat.succ -- 2 #check Nat.succ_le_succ -- ∀ {n m : Nat}, n ≤ m → n.succ ≤ m.succ /-! We apply modus-ponens with `P` being `0 ≤ 2` and `P → Q` being `Nat.succ_le_succ` applied to `0` and `2`. -/ #check @Nat.succ_le_succ 0 2 -- 0 ≤ 2 → 1 ≤ 3 #check modus_ponens (Nat.zero_le 2) (@Nat.succ_le_succ 0 2) -- 1 ≤ 3 /-! Some proofs at *term level* -/ def one_le_three : 1 ≤ 3 := Nat.succ_le_succ (Nat.zero_le 2) -- the `_` is filled in by Lean with `2` as the only way to make the type correct def two_le_five : 2 ≤ 5 := @Nat.succ_le_succ 1 _ (Nat.succ_le_succ (Nat.zero_le _)) #check @Nat.succ_le_succ 0 2 (Nat.zero_le 2) -- 1 ≤ 3 def wrong_three_le_one (h : 2 ≤ 0) : 3 ≤ 1 := Nat.succ_le_succ h /-! ## Proof Irrelevance In Lean, any two proof terms of the same proposition are equal by definition. This is called *proof irrelevance*. -/ theorem proof_irrelevance {P: Prop} (h₁ h₂: P) : h₁ = h₂ := rfl /-! ## Universes * Universes in Lean are called `Sort`s, they are `Sort 0`, `Sort 1`, etc. * `Prop` is `Sort 0`. * `Type` is `Sort 1`. * `Type n` is `Sort n.succ`. * Strictly speaking, the `n` here is a universe level, not a natural number. -/ /-! ## Dependent Function Types The type of `Nat.zero_le` is `∀ (n : Nat), 0 ≤ n`. We will write it more like a function type -/ #check Nat.zero_le -- ∀ (n : Nat), 0 ≤ n def my_zero_le : (n: Nat) → Nat.le 0 n := fun n ↦ Nat.zero_le n #check Nat.le 0 -- Nat → Prop /-! We saw that if `α` and `β` are types (or props), then `α → β` is the type of functions from `α` to `β`. If instead we have `β : α → Type`or `β : α → Prop`, then `(a : α) → β a` (more formally `Π (a : α), β a`) is the type of dependent functions from `a : α` to `β a`. -/