Lean Seminar week 2
This post contains some comments about the week 2 lecture of the Tufts seminar on "Formalization of math and Lean".
Lists (and other things) of a particular type.
Given a type α we pointed out that List α is the type with terms like
[a,b,c] where a b c : α.
There were some questions of the form: "how would I get a list containing terms of differing types?". I scribbled an answer on the board, but let me reiterate this and provide another alternative as well.
-
solution one: use
Sum(In the lecture, I said to use the Haskell name
Either, but as it turns out, Lean actually uses the nameSum.)Given types
α β : Type, the new typeSum α β(or you can writeα ⊕ β) has two constructors:Sum.inl : α → Sum α βandSum.inr : β → Sum α β.So a list with natural numbers and strings might look like this:
example : List (String ⊕ ℕ) := [ Sum.inr 1, Sum.inr 23, Sum.inl "potato" ] -
solution two: define your own inductive type.
inductive MyType | N : ℕ → MyType | S : String → MyType | R : ℚ → MyType deriving ReprThis type has constructors
N,S, andR. Using it, you can make a list containing natural numbers, strings, and rationals:open MyType def l : List MyType := [ N 1, S "potato", R (1/2), N 23 ] #eval l
Like List, Set depends on a type: Set α is sets of things of type α. So the "one type
per collection" constraint applies here too.
Writing the set {1, 2, "potato"} fails for the same reason
[1, 2, "potato"] does, and the fixes are the same: use Set (String ⊕ ℕ) or your own inductive type.
Here is one way that Set α differs from List α, though:
example : ¬ ([1,2,3] : List ℕ) = ([1,2,3,3] : List ℕ) := ⊢ ¬[1, 2, 3] = [1, 2, 3, 3] All goals completed! 🐙
-- sets defined by listing elements aren't affected by repetition
example : ({1,2,3} : Set ℕ) = ({1,2,3,3} : Set ℕ) := ⊢ {1, 2, 3} = {1, 2, 3, 3}
x:ℕ⊢ x ∈ {1, 2, 3} ↔ x ∈ {1, 2, 3, 3} -- sets are equal iff they have the same elements
All goals completed! 🐙
Using names in binders
In the lecture, I had originally written
theorem modus_ponens {p q : Prop} : (f : p → q) → (h : p) → q := p:Propq:Prop⊢ (p → q) → p → q
intro f p:Propq:Propf:p → qh:p⊢ q
All goals completed! 🐙
The signature of a definition or theorem says what it takes as input and what it produces: it lists the arguments, each with its type, and the type of the result. For a theorem, the arguments are the hypotheses and the result type is the statement being proved.
The type of modus_ponens as stated is
(f : p → q) → (h : p) → q
But in this case, there is no real reason for introducing names for the terms in the signature. Thus, we could have written
theorem modus_ponens_shorter {p q : Prop} : (p → q) → p → q := p:Propq:Prop⊢ (p → q) → p → q
intro f p:Propq:Propf:p → qh:p⊢ q
All goals completed! 🐙
Note that sometimes it is useful to give binders in the signature. In the following
example, List.Vector α n is the type of vectors of length n with entries of type α:
open List.Vector in
def rept {α : Type} : α → (n : ℕ) → List.Vector α n := α:Type⊢ α → (n : ℕ) → List.Vector α n
intro a α:Typea:αn:ℕ⊢ List.Vector α n
match n with
α:Typea:αn:ℕ⊢ List.Vector α 0 All goals completed! 🐙
α:Typea:αn✝:ℕn:ℕ⊢ List.Vector α n.succ All goals completed! 🐙
#check rept "aaa" 4 -- List.Vector String 4
#eval rept "aaa" 4
#check rept [1,2] 3 -- List.Vector (List ℕ) 3
#eval rept [1,2] 3
Here in the definition of rept we needed to name the natural number argument
(in this case, n : ℕ) in the signature, because
it is used later in the signature (List.Vector α n).