Wie verstehe ich die Hindley-Milner-Regeln?
Hindley-Milner ist ein Regelwerk in Form eines sequentiellen Kalküls (kein natürlicher Abzug), das zeigt, dass wir den (allgemeinsten) Typ eines Programms aus der Erstellung des Programms ohne explizite Typdeklarationen ableiten können.
Die Symbole und Notation
Lassen Sie uns zunächst die Symbole erläutern und die Priorität des Operators erörtern
- 𝑥 ist eine Kennung (informell ein Variablenname).
- : means ist eine Art von (informell eine Instanz von oder "is-a").
- 𝜎 (Sigma) ist ein Ausdruck, der entweder eine Variable oder eine Funktion ist.
- also wird 𝑥: 𝜎 gelesen " 𝑥 ist-a 𝜎 "
- ∈ bedeutet "ist ein Element von"
- 𝚪 (Gamma) ist eine Umgebung.
- ⊦ (die Behauptungszeichen) Mittel behaupten (oder beweisen, aber kontextuell „behaupten“ besser liest.)
- ⊦ Γ x : σ wird also gelesen "Γ dass x behauptet, ist-a σ "
- 𝑒 ist eine tatsächliche Instanz (Element) vom Typ 𝜎 .
- 𝜏 (Tau) ist ein Typ: entweder einfach, variabel ( 𝛼 ), funktional 𝜏 → 𝜏 ' oder Produkt 𝜏 × 𝜏' (Produkt wird hier nicht verwendet)
- 𝜏 → 𝜏 ' ist ein Funktionstyp, bei dem 𝜏 und 𝜏' möglicherweise unterschiedliche Typen sind.
𝜆𝑥.𝑒 bedeutet, dass 𝜆 (Lambda) eine anonyme Funktion ist, die ein Argument 𝑥 verwendet und einen Ausdruck 𝑒 zurückgibt .
sei 𝑥 = 𝑒₀ in 𝑒₁ bedeutet im Ausdruck 𝑒₁ , ersetze 𝑒₀ wo immer 𝑥 erscheint.
⊑ bedeutet, dass das vorherige Element ein Subtyp (informell - Unterklasse) des letzteren Elements ist.
- 𝛼 ist eine Typvariable.
- ∀ 𝛼.𝜎 ist ein Typ, ∀ (für alle) Argumentvariablen, 𝛼 , der 𝜎 Ausdruck zurückgibt
- ∉ frei (𝚪) bedeutet kein Element der im äußeren Kontext definierten freien Typvariablen von 𝚪. (Gebundene Variablen können ersetzt werden.)
Alles über der Linie ist die Prämisse, alles darunter ist die Schlussfolgerung ( Per Martin-Löf )
Vorrang, zum Beispiel
Ich habe einige der komplexeren Beispiele aus den Regeln genommen und redundante Klammern eingefügt, die Vorrang haben:
- 𝑥: 𝜎 ∈ ∈ könnte geschrieben werden (𝑥: 𝜎) ∈ ∈
𝚪 ⊦ 𝑥 : 𝜎 könnte geschrieben werden 𝚪 ⊦ ( 𝑥 : 𝜎 )
𝚪 ⊦ sei 𝑥 = 𝑒₀ in 𝑒₁ : 𝜏
ist äquivalent 𝚪 ⊦ (( sei ( 𝑥 = 𝑒₀ ) in 𝑒₁ ): 𝜏 )
𝚪 ⊦ 𝜆𝑥.𝑒 : 𝜏 → 𝜏 ' ist äquivalent 𝚪 ⊦ (( 𝜆𝑥.𝑒 ): ( 𝜏 → 𝜏' ))
Dann zeigen große Räume, die Assertionsaussagen und andere Voraussetzungen trennen, eine Reihe solcher Voraussetzungen an, und schließlich bringt die horizontale Linie, die Prämisse von Schlussfolgerung trennt, das Ende der Rangfolge.
Die Regeln
Was hier folgt, sind englische Interpretationen der Regeln, denen jeweils eine lose Anpassung und eine Erklärung folgen.
Variable
Wenn 𝑥 eine Art von 𝜎 (Sigma) ist, ein Element von 𝚪 (Gamma),
schließen Sie, dass 𝚪 behauptet, 𝑥 sei ein 𝜎.
Anders ausgedrückt, in 𝚪 wissen wir, dass 𝑥 vom Typ 𝜎 ist, weil 𝑥 vom Typ 𝜎 in 𝚪 ist.
Dies ist im Grunde eine Tautologie. Ein Bezeichnername ist eine Variable oder eine Funktion.
Funktionsanwendung
Gegeben 𝚪 behauptet, 𝑒₀ sei ein Funktionstyp und 𝚪 behauptet, 𝑒₁ sei ein 𝜏
schlussfolgern 𝚪 behauptet, die Anwendung der Funktion 𝑒₀ auf 𝑒₁ sei ein Typ 𝜏 '
Um die Regel neu zu formulieren, wissen wir, dass die Funktionsanwendung den Typ 𝜏 'zurückgibt, da die Funktion den Typ 𝜏 → 𝜏' hat und ein Argument vom Typ 𝜏 erhält.
Dies bedeutet, dass wenn wir wissen, dass eine Funktion einen Typ zurückgibt und wir ihn auf ein Argument anwenden, das Ergebnis eine Instanz des Typs ist, von dem wir wissen, dass er zurückgibt.
Funktionsabstraktion
Wenn 𝚪 und 𝑥 vom Typ 𝜏 behauptet, dass 𝑒 ein Typ ist, 𝜏 '
Schlussfolgerung 𝚪 eine anonyme Funktion behauptet, erts von 𝜆 Ausdruck zurückgibt, 𝑒 vom Typ 𝜏 → 𝜏' ist.
Wenn wir wieder eine Funktion sehen, die 𝑥 nimmt und einen Ausdruck 𝑒 zurückgibt, wissen wir, dass sie vom Typ 𝜏 → 𝜏 'ist, weil 𝑥 (a 𝜏) behauptet, dass 𝑒 ein 𝜏' ist.
Wenn wir wissen, dass 𝑥 vom Typ 𝜏 ist und somit ein Ausdruck 𝑒 vom Typ 𝜏 'ist, dann ist eine Funktion von 𝑥, die den Ausdruck 𝑒 zurückgibt, vom Typ 𝜏 → 𝜏'.
Lassen Sie die Variablendeklaration
Gegeben 𝚪 behauptet of vom Typ 𝜎 und 𝚪 und 𝑥 vom Typ 𝜎, behauptet 𝑒₁ vom Typ 𝜏,
schließe 𝚪 behauptet let
𝑥 = 𝑒₀ in
𝑒₁ vom Typ 𝜏
Locker ist 𝑥 in 𝑒₁ (a 𝜏) an 𝑒₀ gebunden, weil 𝑒₀ ein 𝜎 ist und 𝑥 ein 𝜎 ist, das behauptet, 𝑒₁ sei ein 𝜏.
Das heißt, wenn wir einen Ausdruck 𝑒₀ haben, der ein 𝜎 ist (eine Variable oder eine Funktion), und einen Namen 𝑥, auch ein 𝜎, und einen Ausdruck 𝑒₁ vom Typ 𝜏, dann können wir 𝑒₀ für 𝑥 ersetzen, wo immer er im Inneren erscheint von 𝑒₁.
Instanziierung
Gegeben 𝚪 behauptet 𝑒 vom Typ 𝜎 'und 𝜎' ist ein Subtyp von 𝜎
schlussfolgern 𝚪 behauptet 𝑒 ist vom Typ 𝜎
Ein Ausdruck 𝑒 ist vom übergeordneten Typ 𝜎, da der Ausdruck 𝑒 vom Subtyp 𝜎 'und 𝜎 vom übergeordneten Typ von 𝜎' ist.
Wenn eine Instanz von einem Typ ist, der ein Subtyp eines anderen Typs ist, dann ist es auch eine Instanz dieses Supertyps - des allgemeineren Typs.
Verallgemeinerung
Wenn 𝚪 behauptet, 𝑒 sei ein 𝜎 und 𝛼 kein Element der freien Variablen von 𝚪 ist,
schließen Sie 𝚪 behauptet 𝑒, geben Sie für alle Argumentausdrücke 𝛼 einen 𝜎-Ausdruck zurück
Im Allgemeinen wird 𝑒 für alle Argumentvariablen (𝛼) eingegeben, die 𝜎 zurückgeben, da wir wissen, dass 𝑒 ein 𝜎 und 𝛼 keine freie Variable ist.
Dies bedeutet, dass wir ein Programm verallgemeinern können, um alle Typen für Argumente zu akzeptieren, die nicht bereits im enthaltenen Bereich gebunden sind (Variablen, die nicht lokal sind). Diese gebundenen Variablen sind austauschbar.
Alles zusammenfügen
Unter bestimmten Voraussetzungen (z. B. keine freien / undefinierten Variablen, eine bekannte Umgebung) kennen wir die Arten von:
- atomare Elemente unserer Programme (variabel),
- von Funktionen zurückgegebene Werte (Funktionsanwendung),
- funktionelle Konstrukte (Funktionsabstraktion),
- let bindings (Let Variable Declarations),
- übergeordnete Instanztypen (Instanziierung) und
- alle Ausdrücke (Generalisierung).
Fazit
Diese Regeln zusammen ermöglichen es uns, den allgemeinsten Typ eines behaupteten Programms zu beweisen, ohne dass Typanmerkungen erforderlich sind.