This commit is contained in:
Jon Eugster
2023-03-01 17:25:10 +01:00
parent 676560a0df
commit a18582a5f8
20 changed files with 303 additions and 185 deletions
@@ -27,9 +27,10 @@ Zum Beispiel ist `ℕ` der Typ der natürlichen Zahlen, `Prop` der Typ der logis
Aussagen, und ein Ring ist ein Typ `(R : Type)` zusammen mit einer Instanz `[Ring R]`,
die sagt, dass auf diesem Typ eine Ringstruktur besteht.
**Achtung**: Wie du aber gleich sehen wirst sind Typen und Mengen in Lean komplett
unterschiedliche Sachen! (Und Mengen nehmen in Lean nicht die fundamentale Funktion
ein, die sie in der Mathe innehaben.)
**Achtung**: Wie du aber gleich sehen wirst sind Typen und Mengen in Lean unterschiedliche
Sachen.
Hier ein kleines Beispiel zu Typen und Instanzen:
"
Statement
@@ -38,5 +39,11 @@ Statement
ring
Hint (R : Type) (h : CommRing R) (a : R) (b : R) : a + b = b + a =>
"Die Taktik `ring` funktioniert in jedem Typen,
der mindestens eine Instanz `[Ring R]` hat."
"
Die Taktik `ring` funktioniert in jedem Typen,
ist aber stärker, je nach Instanz auf dem Typen.
In Mathlib sind Instanzen `[CommSemiring ℕ]`, [CommRing ℤ]`, `[Field ℚ]`, etc. definiert.
Die Taktik `ring` muss eine dieser Instanzen finden, die sagen, dass die Addition kommutative ist,
damit das Lemma `add_comm` angewendet und die Aussage bewiesen werden kann.
"
@@ -1,61 +0,0 @@
import TestGame.Metadata
import Mathlib
set_option tactic.hygienic false
Game "TestGame"
World "LeanStuff"
Level 1
Title ""
Introduction
"
Dieses Kapitel führt ein paar Lean-spezifische Sachen ein, die du wissen solltest.
Mathematisch haben diese Sachen keinen Inhalt, aber es ist wichtig, dass du etwas
verstehst wie Lean manche Sachen macht.
Als erstes geht es um implizite und explizite Argumente von Lemmas. **Explizite Argumente**
schreibt man mit runden Klammern `()`, **impliziete Argumente** mit geschweiften `{}`.
Als implizit werden alle Argumente markiert, die Lean selbständig aus dem Kontext
erschliessen und einfüllen kann.
Als Beispiel hier zweimal das gleiche Lemma, einmal ohne impliziten Argumenten und einmal mit
```
lemma not_or_of_imp' (A B : Prop) (h : A → B) : ¬A ∨ B := sorry
lemma not_or_of_imp {A B : Prop} (h : A → B) : ¬A ∨ B := sorry
```
Hat man nun `g : C → D` dann braucht man diese Lemmas mit
`have := not_or_of_imp g` oder `have := not_or_of_imp' C D g`.
Wie man sieht erschliesst Lean die impliziten Argumente automatisch und es wäre deshalb
unnötig, diese jedes Mal explizit angeben zu müssen.
TODO
(Trick mit `@not_or_of_imp` kann man sagen, dass man **alle** Argumente angeben möchte und mir
`not_or_of_imp g (B := D)` könnte man ein spezifisches implizites Argument spezifizieren.
Wenn man diese Tricks braucht, heisst das aber meistens, das etwas nicht optimal definiert
ist.)
TODO
Zudem gibt es noch Argumente in eckigen Klammern `[]`. Dies sind auch implizite
Argumente, die aber von der **Instance Resolution** gefüllt werden sollen. Dies
wird später kommen, aber ein typisches Beispiel ist `(S : Type _) [ring S]` was Lean
sagt, es soll suchen gehen, ob es weiss, dass `S` ein Ring ist.
"
open Set
Statement
"TODO" : True := by
trivial
NewTactics rw
#check not_not
#check not_or_of_imp
@@ -12,26 +12,40 @@ Title "Universen"
Introduction
"
In der Mathematik stösst man manchmal an Mengentheoretische Grenzen, und so auch in Lean.
Ein weitere Syntax, den man in Lean abundzu sieht sind Universen.
Klassisch ist bekannt dass die \"Menge aller Mengen\" nicht existieren kann.
Diese sind für Mathematiker erst einmal nicht so wichtig, und es reicht zu wissen,
dass diese existieren.
Falls man an diese Grenzen stösst (z.B. in der Mengenlehre oder Kategorientheorie) hat Lean
Universen bereit: `Type` ist eigentlich `Type 0` und es gibt auch `Type 1, Type 2, ...`
Da alle Objekte in Lean einen Typ haben, kann man sich fragen, welchen Typ hat eigentlich `Type`
selber? Die Anwort darauf ist dass `Type` vom Typ `Type 1` ist und dieses wiederum vom Typ `Type 2`
usw.
Deshalb sieht man oft in Lean-Code `(R : Type _)` wenn es keine mengentheoretischen
Probleme gibt (`_` ist ein Platzhalter) oder `universe u` am Anfang und dann `(R : Type u)`
falls man diese mengentheoretischen Probleme kontrollieren muss.
Da Lemmas in Lean gerne so algemein wie möglich formuliert werden, sieht man oft `(R : Type _)`
anstatt `(R : Type)`, wobei `_` einfach ein Platzhalter für eine Zahl ist.
In der Praxis kommt man aber relativ weit, wenn man sich erst mal nicht mit Universen
beschäftigt, und lediglich weiss, dass es sowas gibt.
Alternativ kann man auch explizit Universum-Levels definieren, so sind die folgenden beiden
Aussdrücke äquivalent:
```
variable (R : Type _)
universe u
variable (R : Type u)
```
In der Praxis kann man immer ohne bedenken `Type _` verwendenen und wenn man auf
(mengentheoretische)
Probleme stösst, muss man dan eventuell die Universen spezifizieren.
*Die Normalform ist eigentlich `(R : Type _)` zu schreiben solange man kein Grund hat
das Universum einzuschränken.*
"
Statement
"TODO"
(R S : Type _) [CommRing R] (a b : R) : a + b = b + a := by
""
(R : Type _) [CommRing R] (a b : R) : a + b = b + a := by
ring
Hint (R : Type _) (h : CommRing R) (a : R) (b : R) : a + b = b + a =>
"Die Taktik `ring` funktioniert in jedem Typen,
der mindestens eine Instanz `[Ring R]` hat."
-- Hint (R : Type) (h : CommRing R) (a : R) (b : R) : a + b = b + a =>
-- ""
@@ -1,6 +1,7 @@
import TestGame.Metadata
import Mathlib
import TestGame.Options.BigOperators
set_option tactic.hygienic false
@@ -20,38 +21,43 @@ schreibt man mit runden Klammern `()`, **impliziete Argumente** mit geschweiften
Als implizit werden alle Argumente markiert, die Lean selbständig aus dem Kontext
erschliessen und einfüllen kann.
Als Beispiel hier zweimal das gleiche Lemma, einmal ohne impliziten Argumenten und einmal mit
Als Beispiel schauen wir uns ein bekanntes Lemma an:
```
lemma not_or_of_imp' (A B : Prop) (h : A → B) : ¬A ∨ B := sorry
lemma not_or_of_imp {A B : Prop} (h : A → B) : ¬A ∨ B := sorry
lemma Fin.sum_univ_castSucc {β : Type _} [AddCommMonoid β] {n : ℕ} (f : Fin (n + 1) → β) :
∑ i : Fin (n + 1), f i = ∑ i : Fin n, f (↑Fin.castSucc.toEmbedding i) + f (Fin.last n) := by
sorry
```
Hat man nun `g : C → D` dann braucht man diese Lemmas mit
`have := not_or_of_imp g` oder `have := not_or_of_imp' C D g`.
Wie man sieht erschliesst Lean die impliziten Argumente automatisch und es wäre deshalb
unnötig, diese jedes Mal explizit angeben zu müssen.
TODO
(Trick mit `@not_or_of_imp` kann man sagen, dass man **alle** Argumente angeben möchte und mir
`not_or_of_imp g (B := D)` könnte man ein spezifisches implizites Argument spezifizieren.
Wenn man diese Tricks braucht, heisst das aber meistens, das etwas nicht optimal definiert
ist.)
Nebenbemerkung: Es gibt auch noch implizite **Klassen-Elemente** mit eckigen Klammern `[]`
wie zum Beispiel `[CommRing R]` im vorigen Beispiel. Diese werden später behandelt,
und sagen Lean, dass es für dieses Argument eine **Instanz** suchen gehen soll. Diese
Instanzen werden mehrheitlich dafür verwendet, mathematische Strukturen auf Typen zu
definieren, aber das kommt alles später.
Hier ist unter anderem `n` als implizites Argument angegeben, da Lean aus `f` herauslesen kann,
was `n` sein muss. Falls man trotzdem einmal das implizites Argument angeben muss
(z.B. um `rw` zu helfen, wenn es mehrere Möglichkeiten gibt),
kann man dies mit `Fin.sum_univ_castSucc (n := m + 1)` machen.
"
Statement
"TODO"
(R S : Type _) [CommRing R] (a b : R) : a + b = b + a := by
ring
open BigOperators
Hint (R : Type _) (h : CommRing R) (a : R) (b : R) : a + b = b + a =>
"Die Taktik `ring` funktioniert in jedem Typen,
der mindestens eine Instanz `[Ring R]` hat."
Statement
"Zeige $(\\sum_{i=0}^{m} i) + (m + 1) = \\sum_{i=0}^{m + 1} i$."
(m : ℕ) :
∑ i : Fin (m + 1), (i : ℕ) + (m + 1) = ∑ i : Fin (Nat.succ m + 1), ↑i := by
rw [Fin.sum_univ_castSucc (n := m + 1)]
rfl
OnlyTactics rw rfl
NewLemmas Fin.sum_univ_castSucc
HiddenHint (m : ℕ) :
∑ i : Fin (m + 1), (i : ℕ) + (m + 1) = ∑ i : Fin (Nat.succ m + 1), ↑i =>
"Das Lemma `Fin.sum_univ_castSucc` hilft."
Hint (m : ℕ) :
∑ i : Fin m, (Fin.castSucc.toEmbedding i : ℕ) + ↑(Fin.last m) + (m + 1) =
∑ i : Fin (Nat.succ m + 1), ↑i =>
"Hier hat `rw` die falsche der beiden Summen umgeschrieben. Hilf ihm mit
`rw [Fin.sum_univ_castSucc (n := m + 1)]`."
Hint (m : ℕ) :
∑ i : Fin (m + 1), (i : ℕ) + (m + 1) =
∑ i : Fin (m + 1), ↑i + (m + 1) =>
"Jetzt sind beide Seiten gleich und das Goal kann mit `rfl` geschlossen werden."
@@ -0,0 +1,78 @@
import TestGame.Metadata
import Mathlib
import TestGame.Options.BigOperators
set_option tactic.hygienic false
Game "TestGame"
World "LeanStuff"
Level 4
Title "Instanz-Argumente"
Introduction
"
Bezüglich impliziten Argumente gibt es noch einige weitere Punkte oder Tricks,
die man wissen sollte.
* Instanz-Argumente wie `[Ring R]` sind auch impilzite Argumente. Der Unterschied ist, dass
Lean einen anderen Mechanismus braucht, um diese zu füllen: Es sucht nach einer entsprechenden
*Instanz* und, setzt die erste solche Instanz ein.
Ausserhalb eines Beweises könnte man auch mit
```
#synth Ring ℤ
```
testen, ob Lean eine ensprechende Instanz findet. Instanzen werden dafür gebraucht, Typen
mit (algebraischer) Stukturen zu versehen.
* Ein `_` irgendwo im Lean-Code ist immer ein Platzhalter, den Lean versucht aus dem Kontext zu
füllen. Das kann praktisch sein, wenn man etwas nicht ausschreiben will, das offensichtlich ist.
* Mit `@` kann man forcieren, dass alle Argumente explizit sind.
Für ein Lemma
```
lemma not_or_of_imp {A B : Prop} (h : A → B) :
¬A ∨ B := sorry
```
heisst das zum Beispiel dass `not_or_of_imp g` das gleiche ist wie
`@not_or_of_imp _ _ g`.
Und `Fin.sum_univ_castSucc (n := m + 1)` könnte man auch als
`@Fin.sum_univ_castSucc _ _ (m + 1)` schreiben.
"
open BigOperators
Statement
"Zeige $(\\sum_{i=0}^{m} i) + (m + 1) = \\sum_{i=0}^{m + 1} i$."
(m : ℕ) :
∑ i : Fin (m + 1), (i : ℕ) + (m + 1) = ∑ i : Fin (Nat.succ m + 1), ↑i := by
rw [Fin.sum_univ_castSucc (n := m + 1)]
rfl
OnlyTactics rw rfl
NewLemmas Fin.sum_univ_castSucc
Hint (m : ℕ) :
∑ i : Fin (m + 1), (i : ℕ) + (m + 1) = ∑ i : Fin (Nat.succ m + 1), ↑i =>
"
Probier nochmals das gleiche, diesmal mit
```
rw [@Fin.sum_univ_castSucc _ _ (m + 1)]
```
anstatt
```
rw [Fin.sum_univ_castSucc (n := m + 1)]
```
"
Hint (m : ℕ) :
∑ i : Fin m, (Fin.castSucc.toEmbedding i : ℕ) + ↑(Fin.last m) + (m + 1) =
∑ i : Fin (Nat.succ m + 1), ↑i =>
"Sackgasse!"
Hint (m : ℕ) :
∑ i : Fin (m + 1), (i : ℕ) + (m + 1) =
∑ i : Fin (m + 1), ↑i + (m + 1) =>
"Jetzt sind beide Seiten gleich und das Goal kann mit `rfl` geschlossen werden."