|
| 1 | +import Mathlib.Tactic.Basic |
| 2 | + |
| 3 | +set_option linter.style.emptyLine false |
| 4 | + |
| 5 | +/- |
| 6 | +# Introduction to Lean as a Programming Language |
| 7 | +===================== |
| 8 | +
|
| 9 | +## Basic Values and Printing |
| 10 | +In Lean, we declare values using `def`. The type can be inferred or explicitly stated. |
| 11 | +-/ |
| 12 | + |
| 13 | +-- Basic Hello World |
| 14 | +def hello : String := "Hello, World!" |
| 15 | +#check hello |
| 16 | + |
| 17 | +-- Types can be infered |
| 18 | +def hello2 := "Hello, World!" |
| 19 | +#check hello2 |
| 20 | + |
| 21 | +/- |
| 22 | +Compare with: |
| 23 | +Python: message = "Hello, World!" # Dynamic typing |
| 24 | +C: const char* hello = "Hello, World!"; // Static typing |
| 25 | +-/ |
| 26 | + |
| 27 | +def printHello : IO Unit := -- 'IO Unit' is an Explicit type for IO operations |
| 28 | + IO.println hello |
| 29 | + |
| 30 | +#check printHello -- tells us this is of type 'IO Unit' |
| 31 | +#eval printHello -- actually executes the method and prints "Hello, World!" |
| 32 | + |
| 33 | + |
| 34 | +/- |
| 35 | +## Basic Arithmetic |
| 36 | +Lean uses natural numbers (Nat) by default for integers. Functions can be defined |
| 37 | +with explicit type annotations, similar to C but with a different syntax. |
| 38 | +-/ |
| 39 | + |
| 40 | +def add (x y : Nat) : Nat := x + y |
| 41 | + |
| 42 | +/- |
| 43 | +Compare with: |
| 44 | +Python: def add(x, y): return x + y |
| 45 | +C: int add(int x, int y) { return x + y; } |
| 46 | +-/ |
| 47 | + |
| 48 | +#eval add 2 3 -- 2 + 3 = 5 |
| 49 | +#check add -- 'add' has type Nat → Nat → Nat |
| 50 | +#check add 2 3 -- 2 + 3 = 5 is of type Nat |
| 51 | +#check add 2 -- we can partially apply: 'add 2' is of type Nat → Nat |
| 52 | + |
| 53 | +def triple_multiply (x y z : Nat) : Nat := x * y * z |
| 54 | + |
| 55 | +#eval triple_multiply 1 2 3 -- 1 * 2 * 3 = 6 |
| 56 | +#check triple_multiply 1 2 3 -- 1 * 2 * 3 = 6 is of type Nat |
| 57 | +#check triple_multiply -- 'triple_multiply' is of type Nat → Nat → Nat → Nat |
| 58 | + |
| 59 | + |
| 60 | +/- |
| 61 | +## Pattern Matching and Control Flow |
| 62 | +Lean uses pattern matching as its primary control flow mechanism. This is more |
| 63 | +powerful than traditional if/switch statements found in C or Python. |
| 64 | +-/ |
| 65 | + |
| 66 | +def calculator (op : String) (x y : Nat) : Nat := |
| 67 | + match op with |
| 68 | + | "+" => x + y |
| 69 | + | "-" => x - y |
| 70 | + | "*" => x * y |
| 71 | + | _ => 0 -- default |
| 72 | + |
| 73 | +/- |
| 74 | +Compare with: |
| 75 | +Python: |
| 76 | +def calculator(op, x, y): |
| 77 | + if op == "+": return x + y |
| 78 | + elif op == "-": return x - y |
| 79 | + elif op == "*": return x * y |
| 80 | + else: return 0 |
| 81 | +
|
| 82 | +C: |
| 83 | +int calculator(char* op, int x, int y) { |
| 84 | + if (strcmp(op, "+") == 0) return x + y; |
| 85 | + else if (strcmp(op, "-") == 0) return x - y; |
| 86 | + else if (strcmp(op, "*") == 0) return x * y; |
| 87 | + else return 0; |
| 88 | +} |
| 89 | +-/ |
| 90 | + |
| 91 | +#eval calculator "*" 2 3 -- 2 * 3 = 6 |
| 92 | +#check calculator -- 'calculator' is of type String → Nat → Nat → Nat |
| 93 | + |
| 94 | + |
| 95 | +/- |
| 96 | +## String Interpolation |
| 97 | +Lean provides string interpolation similar to modern programming languages. |
| 98 | +-/ |
| 99 | + |
| 100 | +def greeting (name : String) : String := |
| 101 | + s!"Hello, {name}!" |
| 102 | + |
| 103 | +/- |
| 104 | +Compare with: |
| 105 | +Python: |
| 106 | +def greeting(name): |
| 107 | + return f"Hello, {name}!" |
| 108 | +-/ |
| 109 | + |
| 110 | +#eval greeting "Martin" -- "Hello, Martin!" |
| 111 | +#check greeting -- 'greeting' is of type String → String |
| 112 | +#check greeting "Martin" -- 'greeting "Martin"' is of type String |
| 113 | + |
| 114 | + |
| 115 | +/- |
| 116 | +## Type Inference |
| 117 | +Lean has a powerful type inference system that can automatically determine types in many cases. |
| 118 | +This makes code more concise while maintaining type safety. The compiler will infer the most |
| 119 | +general type that satisfies the constraints. |
| 120 | +-/ |
| 121 | + |
| 122 | +-- Type inference for simple values |
| 123 | +def inferredNumber := 42 -- Inferred as Nat |
| 124 | +def inferredText := "Hello" -- Inferred as String |
| 125 | +def inferredList := [1, 2, 3] -- Inferred as List Nat |
| 126 | + |
| 127 | +-- def mixedList := [1, "test"] -- Fails because List cannot have mixed type elements |
| 128 | + |
| 129 | +#check inferredList |
| 130 | + |
| 131 | + |
| 132 | +-- Type inference for functions |
| 133 | +def inferredAdd (x : Nat) y := x + y -- type of `y` and of output is inferred as `Nat` |
| 134 | +def inferredConcat (x : String) y := x ++ y -- type of `y` and output is inferred as `String` |
| 135 | + |
| 136 | +-- Sometimes explicit types are clearer or necessary |
| 137 | +def explicitSubNat (x y : Nat) := x - y -- Forces `Nat` arithmetic |
| 138 | +#check explicitSubNat -- Nat → Nat → Nat |
| 139 | +#check explicitSubNat 2 3 -- Nat |
| 140 | +#eval explicitSubNat 2 3 -- 2 - 3 = 0 in Nat |
| 141 | + |
| 142 | +def explicitSubInt (x y : Int) := x - y |
| 143 | +#check explicitSubInt 2 3 -- Int |
| 144 | +#eval explicitSubInt 2 3 -- 2 - 3 = -1 in Int |
| 145 | + |
| 146 | +-- def implictSub (x y) := x - y -- unable to infer type |
| 147 | +-- def implictSub (x y) : Int := x - y -- unable to infer type |
| 148 | +def implictSub (x : Int) y := x - y -- able to infer Int for y and output |
| 149 | +def implictSub' x (y : Int) := x - y -- able to infer Int for x and output |
| 150 | + |
| 151 | +#check implictSub' |
| 152 | +#eval implictSub' 2 3 |
| 153 | + |
| 154 | +/- |
| 155 | +Compare with: |
| 156 | +Python: Type hints are optional |
| 157 | +def add(x, y): # No types needed |
| 158 | + return x + y |
| 159 | +
|
| 160 | +TypeScript: Type inference with explicit options |
| 161 | +let inferredNumber = 42; // number |
| 162 | +let explicitNumber: number = 42; |
| 163 | +-/ |
| 164 | + |
| 165 | +/- |
| 166 | +## Type Coercion |
| 167 | +Some types can be coerced into other types, like Nat to Int. |
| 168 | +-/ |
| 169 | + |
| 170 | +def implictSub'' (x : Nat) (y : Int) := x - y -- able to coerce y into Int and output |
| 171 | + |
| 172 | +#check implictSub'' |
| 173 | +#eval implictSub'' 2 3 |
| 174 | + |
| 175 | +def implictSub''' (x : Int) (y : Nat) := x - y -- able to coerce y to Int |
| 176 | + |
| 177 | +#check implictSub''' |
| 178 | +#eval implictSub''' 2 3 |
| 179 | + |
| 180 | +def inferredAdd' (x : Nat) (y : Int) := x + y |
| 181 | + |
| 182 | +def coercedOutputAdd (x y : Nat) : Int := x - y |
| 183 | + |
| 184 | +#check coercedOutputAdd 2 3 -- Nat → Nat → Int, but it uses the |
| 185 | + -- Int subtraction and coereces the Nat to Int |
| 186 | +#eval coercedOutputAdd 2 3 -- 2 - 3 = -1 since x and y are both first coerced to Int |
| 187 | + |
| 188 | +/- |
| 189 | +## Data Structures |
| 190 | +Lean provides several ways to structure data. Here we demonstrate: |
| 191 | +1. Simple structures (similar to C structs or Python classes) |
| 192 | +2. Namespace organization |
| 193 | +3. Method-like function definitions |
| 194 | +-/ |
| 195 | + |
| 196 | +structure Rectangle where |
| 197 | + width : Float |
| 198 | + height : Float |
| 199 | +deriving Repr |
| 200 | + |
| 201 | +def myRectangle : Rectangle := { width := 4.0, height := 2.0 } |
| 202 | + |
| 203 | +def Rectangle.area (r : Rectangle) : Float := |
| 204 | + r.width * r.height |
| 205 | + |
| 206 | +#eval Rectangle.area myRectangle |
| 207 | +#eval myRectangle.area |
| 208 | + |
| 209 | +def Rectangle.perimeter (r : Rectangle) := |
| 210 | + 2.0 * (r.width + r.height) |
| 211 | + |
| 212 | +#eval myRectangle.perimeter |
| 213 | + |
| 214 | +structure Point where |
| 215 | + x : Float |
| 216 | + y : Float |
| 217 | +deriving Repr |
| 218 | + |
| 219 | +/- |
| 220 | +Compare with: |
| 221 | +Python: |
| 222 | +class Point: |
| 223 | + def __init__(self, x, y): |
| 224 | + self.x = x |
| 225 | + self.y = y |
| 226 | +
|
| 227 | +C: |
| 228 | +struct Point { |
| 229 | + double x; |
| 230 | + double y; |
| 231 | +}; |
| 232 | +-/ |
| 233 | + |
| 234 | +structure Circle where |
| 235 | + center : Point |
| 236 | + radius : Float |
| 237 | +deriving Repr |
| 238 | + |
| 239 | +def π : Float := 3.14159265358979323846 -- don't do this!! |
| 240 | + |
| 241 | +-- Instead of 'Rectangle.' we can also use 'namespace' |
| 242 | +namespace Circle |
| 243 | + |
| 244 | +-- putting this into the namespace has the same effect |
| 245 | +-- as naming it Circle.area |
| 246 | +def area (c : Circle) : Float := |
| 247 | + π * c.radius * c.radius |
| 248 | + |
| 249 | +def circumference (c : Circle) : Float := |
| 250 | + 2.0 * π * c.radius |
| 251 | + |
| 252 | +def containsPoint (c : Circle) (p : Point) : Bool := |
| 253 | + let dx := c.center.x - p.x |
| 254 | + let dy := c.center.y - p.y |
| 255 | + dx * dx + dy * dy ≤ c.radius * c.radius |
| 256 | + |
| 257 | +end Circle |
| 258 | + |
| 259 | +def myCircle : Circle := { |
| 260 | + center := { x := 1.0, y := 1.0 } |
| 261 | + radius := 2.5 |
| 262 | +} |
| 263 | + |
| 264 | +#eval myCircle -- Shows the full structure |
| 265 | +#eval myCircle.area -- Calculates area |
| 266 | +#eval myCircle.circumference -- Calculates circumference |
| 267 | +#eval myCircle.containsPoint { x := 2.0, y := 2.0 } -- Tests point containment |
| 268 | + |
| 269 | +/- |
| 270 | +------------------------------------------------------------- |
| 271 | +## Propositions as Types – A Glimpse into Proofs in Lean |
| 272 | +
|
| 273 | +In Lean, every proposition is just a type, and a proof is a value (or term) of that type. |
| 274 | +This is the essence of the propositions-as-types (or Curry–Howard) correspondence. |
| 275 | +In other words, proving a proposition amounts to constructing a term that inhabits the type |
| 276 | +representing that proposition. |
| 277 | +------------------------------------------------------------- |
| 278 | +-/ |
| 279 | + |
| 280 | +-- This function claims it returns a Nat |
| 281 | +def t1 : Nat := 0 -- putting a string here would be a type error |
| 282 | + |
| 283 | +#check t1 -- Nat |
| 284 | +#eval t1 -- 0 |
| 285 | + |
| 286 | +def t2 : Nat := 0 |
| 287 | + |
| 288 | +def t3 (n : Nat) : Nat := n |
| 289 | + |
| 290 | +-- def t4' n := n -- Thus doesn't work because Lean cannot infer a type |
| 291 | + |
| 292 | +/- |
| 293 | +Compare with: |
| 294 | +Python: |
| 295 | +def foo(x): |
| 296 | + return x |
| 297 | +-/ |
| 298 | + |
| 299 | +-- But we can "hack" our way around this by making |
| 300 | +-- the arbitrary type of 'n' an argument of the method |
| 301 | +def t4 (T : Type) (n : T) : T := n |
| 302 | + |
| 303 | +#eval t4 Nat 2 |
| 304 | + |
| 305 | +def t4' {T : Type} (n : T) : T := n -- curly brackets make T implicit |
| 306 | + |
| 307 | +#eval t4' 2 |
| 308 | + |
| 309 | +-- We can prove that t3 and t4 applied to Nat return the same output! |
| 310 | +def t4_Nat_eq_t3 : t4 Nat = t3 := rfl |
| 311 | + |
| 312 | +-- doesn't really matter if we use 'def' or 'theorem' here |
| 313 | +theorem t4_Nat_eq_t3' : t4 Nat = t3 := rfl |
| 314 | + |
| 315 | +-- This does not work because not only the type is checked (Nat) |
| 316 | +-- but also the specific instance, which is not the same (0 != n) |
| 317 | +-- example : t4 Nat = t2 := rfl |
| 318 | + |
| 319 | +-- A constructive proof of the type of the statement `P → P` |
| 320 | +def t5 (P : Prop) (p : P) : P := p |
| 321 | + |
| 322 | +theorem t6 (P : Prop) (p : P) : P := by |
| 323 | + exact p -- same proof / method |
| 324 | + |
| 325 | + |
| 326 | +-- Blurring the lines between programming a method and writing a proof: |
| 327 | +-- How to proof P ∧ Q → P |
| 328 | + |
| 329 | +-- Term mode proof |
| 330 | +theorem t7 (P Q : Prop) : P ∧ Q → P := fun ⟨p, _⟩ => p |
| 331 | + |
| 332 | +theorem t7' (P Q : Prop) : (P ∧ Q → P : Prop) := fun ⟨p, _⟩ => p |
| 333 | + |
| 334 | +-- Same proof in tactic mode |
| 335 | +theorem t7'' (P Q : Prop) : P ∧ Q → P := by |
| 336 | + intro ⟨p, _⟩ |
| 337 | + exact p |
| 338 | + |
| 339 | +-- They all have the same type and hence are proving the same theorem |
| 340 | +#check t7 -- ∀ (P Q : Prop), P ∧ Q → P |
| 341 | +#check t7' -- ∀ (P Q : Prop), P ∧ Q → P |
| 342 | +#check t7'' -- ∀ (P Q : Prop), P ∧ Q → P |
| 343 | + |
| 344 | +-- sorry skips the proof but type checker is happy |
| 345 | +example (P Q : Prop) : P ∧ Q → P := by sorry |
| 346 | + |
| 347 | +-- axioms don't require proofs! |
| 348 | +-- but this one is unnecessary, since it is inferred by our type system |
| 349 | +axiom this_is_our_first_axiom (P Q : Prop) : P ∧ Q → P |
0 commit comments