Wie sollte man die Notation von Typsystemen lesen?
(langdev.stackexchange.com)- Auch wenn sich die Notation von Typsystemen je nach Material unterscheidet, lassen sich die meisten Varianten verstehen, wenn man das gemeinsame Gerüst aus Grammatik, Typisierungsrelationen und Inferenzregeln kennt.
- Typsysteme arbeiten auf der abstrakten Syntax einer Sprache, daher muss man zuerst die Terme mit Typen und die Typen selbst grammatisch unterscheiden.
⊢ e: τist ein Typisierungsurteil und wird gelesen als „Der Ausdruckehat den Typτ“; eine Inferenzregel liest man so, dass die Schlussfolgerung unter dem Strich gilt, wenn alle Bedingungen über dem Strich gelten.- Sobald Variablen und Funktionen hinzukommen, wird ein Kontext ergänzt, etwa in
Γ ⊢ e: τ, um Variablennamen und Typen im aktuellen Scope zu verfolgen. - Viele Typisierungsregeln lassen sich wie rekursive Typprüfungsfunktionen lesen, aber nicht jedes logische Urteil wird unmittelbar zu einem entscheidbaren Typprüfungs-Algorithmus.
Typsystem-Notation, ausgehend von der Grammatik
- Ein Typsystem ist ein syntaktisches System einer Programmiersprache und besteht aus einer Menge von Regeln, die auf der abstrakten Syntax der Sprache arbeiten.
- Eine umfassende Beschreibung eines Typsystems stellt meist zuerst die behandelten Syntaxstrukturen als Grammatik dar und notiert sie dann in BNF.
- Selbst in der einfachsten Typsprache zerfällt die Syntax grob in zwei Kategorien:
e: Ausdrücke (expressions), die einen Typ habenτ: Typen (types), die an Ausdrücke gebunden werden
- Die Beispielsprache enthält Boolesche Literale, Integer-Literale, Bedingungsausdrücke, arithmetische Operationen und Vergleichsoperationen als Ausdrücke und verwendet
BoolundIntals Typen. - Das Typsymbol wird je nach Quelle statt
τauch alst,T,σoder mit anderen griechischen Kleinbuchstaben geschrieben, die Gesamtstruktur bleibt aber ähnlich. - Komplexere Sprachen können weitere syntaktische Kategorien enthalten, etwa Anweisungen oder Pattern-Matching-Muster.
Typisierungsrelationen und Urteile lesen
- Nachdem die Grammatik festgelegt ist, definiert man gewöhnlich eine Typisierungsrelation der Form
e : τ.1 + 2 : Intbedeutet: „1 + 2hat den TypInt“.1 + 2 : Boolwürde bedeuten, dass derselbe Ausdruck den TypBoolhat, was nicht passt.true + 2 : Intist schon als Ausdruck selbst unsinnig und hat daher überhaupt keinen Typ.
⊢ e : τist ein Typisierungsurteil;⊢kann man als „die folgende Aussage ist wahr“ lesen.- Regeln ohne irgendetwas über dem Strich sind immer wahre Axiome.
⊢ true : Bool⊢ false : Bool- Regeln für Integer-Literale wie
⊢ 0 : Int,⊢ 1 : Int,⊢ -1 : Int
- Regeln mit Voraussetzungen über dem Strich und einer Folgerung darunter sind Inferenzregeln.
- Wenn alle Bedingungen oben wahr sind, ist auch die Schlussfolgerung unten wahr.
- Wenn
e₁unde₂beideIntsind, dann iste₁ + e₂vom TypInt. - Wenn
e₁unde₂beideIntsind, dann iste₁ < e₂vom TypBool.
Bedingungsausdrücke und Typvariablen
- Die beiden Zweige von
if ... then ... else ...können jeden Typ haben, müssen aber denselben Typ haben.if true then 1 else 2ist möglich.if true then false else trueist möglich.if true then 1 else trueist nicht möglich.
- Um das auszudrücken, verwendet die Regel eine Variable
τfür den Typ der Zweige.- Der Bedingungsausdruck
e₁mussBoolsein. - Der
then-Zweige₂und derelse-Zweige₃müssen denselben Typτhaben. - Der Typ des gesamten Bedingungsausdrucks ist ebenfalls
τ.
- Der Bedingungsausdruck
- Beim Anwenden der Regel kann man für
τjeden Typ wählen, innerhalb derselben Regel muss diese Wahl aber konsistent bleiben.
Inferenzregeln wie Algorithmen lesen
- Diese Notation stammt aus der formalen Logik, und die Art, Typsysteme zu spezifizieren, ähnelt besonders der natürlichen Deduktion.
- Solche Regeln werden verwendet, um formale Beweise über Eigenschaften eines Systems zu konstruieren, und sind wichtig, um Eigenschaften wie Typsicherheit zu beweisen.
- Ein logisches Urteil entspricht nicht immer unmittelbar einem entscheidbaren Typprüfungsalgorithmus.
- In vielen Fällen kann man
⊢ e : τwie eine Funktion lesen, die aus dem Ausdruckeden Typτgewinnt.- Für jede Ausdrucksform in der Grammatik gibt es meist genau eine Regel.
- Jede Typisierungsregel kann als ein Zweig einer rekursiven Typprüfungsfunktion verstanden werden.
- Die Beispiel-Funktion
inferentspricht dabei folgendem Ablauf:trueoderfalseergibtBool- Integer-Literale ergeben
Int - Bei
e₁ + e₂wird geprüft, ob beide InferenzresultateIntsind; dann ist das ErgebnisInt - Bei
e₁ < e₂wird geprüft, ob beide SeitenIntsind; dann ist das ErgebnisBool - Bei
if e₁ then e₂ else e₃wird geprüft, ob die BedingungBoolist und ob beide Zweige denselben Typ haben; dann wird dieser Typ zurückgegeben
- Auch wenn sich Regeln nicht direkt in einen Algorithmus übersetzen lassen, versteht man den Informationsfluss leichter, wenn man
eals Eingabe undτals Ausgabe betrachtet.
Variablen und Kontexte
- Um nützliche Programmiersprachen zu behandeln, braucht man Variablen; das Beispiel wird durch Funktionen zu einer Form des simply typed lambda calculus erweitert.
- Die erweiterte Grammatik enthält:
- Variablen
x - Funktionsabstraktionen
λx:τ. e - Funktionsanwendungen
e e - Funktionstypen
τ → τ
- Variablen
λx:τ. eentspricht in TypeScript(x:τ) => e, undf xentsprichtf(x).- Der Typ einer Variablen hängt vom Kontext ab, in dem sie vorkommt; deshalb kann man keine einfache Regel der Form
⊢ x : ???schreiben. - Daher wird das Typisierungsurteil zu
Γ ⊢ e : τerweitert.Γist der Kontext oder die Typumgebung.⊢trennt die kontextuellen Annahmen links von der rechts zu beweisenden Aussage.- Gelesen wird das als: „Unter dem Kontext
Γhat der Ausdruckeden Typτ."
- Algorithmisch kann man
Γals zusätzliche Eingabe vom TypMap<Variable, Type>auffassen. - Formal wird auch der Kontext selbst als syntaktische Struktur angegeben.
∅: der leere KontextΓ, x:τ: ein Kontext mit zusätzlicher Variablenbindung- Manchmal wird statt
∅auch•für den leeren Kontext verwendet
- In dieser Schreibweise ist der Kontext am ehesten eine association list, die Variablennamen auf Typen abbildet.
Was der Kontext innerhalb der Regeln tut
- Viele Typisierungsregeln verändern den Kontext nicht, sondern reichen ihn einfach weiter.
Γ ⊢ true : Bool- Wenn
Γ ⊢ e₁ : IntundΓ ⊢ e₂ : Int, dannΓ ⊢ e₁ + e₂ : Int
- Bei Variablenverwendung und Lambda-Regeln spielt der Kontext eine zentrale Rolle.
- Wenn
x:τ ∈ Γ, dannΓ ⊢ x : τ - Wenn
Γ, x:τ₁ ⊢ e : τ₂, dannΓ ⊢ (λx:τ₁. e) : τ₁ → τ₂
- Wenn
- Beim Typprüfen des Lambda-Rumpfs
ewird der Kontext um die neue Bindungx:τ₁erweitert. - Die Variablenregel besagt: Wenn im aktuellen Kontext eine Variablenbindung existiert, dann hat die Variable diesen Typ.
- Der Kontext dient als Kommunikationsmechanismus, der Informationen zwischen Lambda-Regel und Variablenregel überträgt.
- Zur Vereinfachung setzen solche Typsystemspezifikationen meist voraus, dass alle Variablen bereits aufgelöst und eindeutig gemacht wurden; Variablenschatten wird nicht behandelt.
- Die Regel für Funktionsanwendung prüft den Typ des Funktionsausdrucks und des Argumentausdrucks gemeinsam.
e₁muss den Typτ₁ → τ₂habene₂muss den Typτ₁haben- Die gesamte Anwendung
e₁ e₂hat dann den Typτ₂
Häufige zusätzliche Notation
- Inferenzregeln werden nicht immer nur vertikal geschrieben.
- Mehrere Bedingungen können horizontal nebeneinander stehen.
- Vertikale und horizontale Anordnung können innerhalb derselben Regel gemischt werden.
- Die Voraussetzungen über dem Strich sind meist andere Urteile, es können aber auch Nebenbedingungen (side conditions) als beliebige Boolesche Bedingungen auftauchen.
x:τ ∈ Γin der Variablenregel ist ein Beispiel.- In algorithmischen Typsystemen kann
α freshverwendet werden; das bedeutet, dassαeine neue Typvariable sein muss, die sich von allen anderen Typvariablen unterscheidet.
Subtyping
- Subtyping ist eine Relation, die Übereinstimmung zwischen Typen schwächer als strikte Gleichheit behandelt, und sie muss explizit definiert werden.
- Gewöhnlich schreibt man
τ₁ <: τ₂und liest das als: „τ₁ist ein Subtyp vonτ₂“. - Eine einfache Subtyping-Relation kann einen Top-Typ
⊤und einen Bottom-Typ⊥einführen.τ <: τ: Jeder Typ ist ein Subtyp seiner selbstτ <: ⊤: Jeder Typ ist ein Subtyp von⊤⊥ <: τ:⊥ist ein Subtyp jedes Typs
- Die erste Regel ist die Reflexivitätsregel und wird oft zu
reflabgekürzt. - Wenn man Subtyping zulassen will, muss die Relation in jeder Typisierungsregel, die es erlauben soll, explizit verwendet werden.
- In der Regel für Funktionsanwendung kann man die Anwendung zulassen, wenn der Argumenttyp
τ₁ein Subtyp des Parametertypsτ₂ist.
- In der Regel für Funktionsanwendung kann man die Anwendung zulassen, wenn der Argumenttyp
Mehrere Kontexte und bidirektionales Typprüfen
- Manche Typsysteme definieren Typisierungsurteile mit mehr als einem Kontext.
- Ein zweiter Kontext wird meist
Δgenannt. Γ;Δ ⊢ e : τwird oft verwendet, wenn beide Kontexte wie Eingaben dienen.Γ ⊢ e : τ ⊣ Δwird oft verwendet, wennΔwie eine Ausgabe behandelt wird.
- Ein zweiter Kontext wird meist
- Der zweite Kontext kann je nach Zweck unterschiedlich verwendet werden.
- Er kann festlegen, dass bestimmte Variablen nur innerhalb eines bestimmten Ausdrucks referenziert werden dürfen.
- In ressourcenbewussten Programmiersprachen kann er als Ausgabekontext dienen, um nachzuverfolgen, welche Variablen verbraucht wurden.
- Bidirektionales Typprüfen ist ein Ansatz, um eingeschränkte nichtlokale Typinferenz ohne Constraint-Solver durchzuführen.
- Ein bidirektionales System teilt das allgemeine Urteil
Γ ⊢ e : τin zwei spezialisierte Urteile auf.Γ ⊢ e ⇐ τ: ein Checking-Urteil, das prüft, ob der Ausdruckeden erwarteten Typτhat; algorithmisch istτeine EingabeΓ ⊢ e ⇒ τ: ein Inference-Urteil, das verwendet wird, wenn keine erwartete Typinformation vorliegt; algorithmisch istτeine Ausgabe
- Beide Urteile werden wechselseitig rekursiv definiert und geben Typinformationen in beide Richtungen weiter.
- In diesem Ansatz können einige Typannotationen weggelassen werden; insbesondere kann die Prüfregel für Lambda-Abstraktionen den Parametertyp aus dem erwarteten Funktionstyp übernehmen, sodass die Annotation am Variablenbinder entfallen kann.
1 Kommentare
Meinungen auf Hacker News
Guy Steele hat vor einiger Zeit einen Vortrag zu diesem Thema gehalten. Einigen Notationen hat er sogar suchbare Namen gegeben, zum Beispiel zweidimensionale Diagramme für Inferenzregeln.
Er nennt das Metanotation der Informatik, aber persönlich wirkt es auf mich eher wie etwas aus der Theorie der Programmiersprachen. https://m.youtube.com/watch?v=dCuZkaaou0Q
https://en.wikipedia.org/wiki/Guy_Steele Guy Steele
https://www.codemesh.io/codemesh2017/guy-l-steele Vortrag „A Cobbler's Child“ auf der Code Mesh 2017
https://www.youtube.com/watch?v=qNPlDnX6Mio „A Cobbler's Child“ (Video auf YouTube)
https://www.youtube.com/watch?v=dCuZkaaou0Q „It's Time for a New Old Language“ (Video auf YouTube)
https://news.ycombinator.com/item?id=15473199 Diskussion auf HN
https://labs.oracle.com/pls/apex/f?p=94065:40150:0::::P40150... Folien
Es ist seltsam, dass Menschen, die mit höchster Präzision über die präzisesten menschlichen Künste sprechen wollen, mehrdeutige und inkonsistente Notation verwenden.
Diese Notation geht bis auf Frege zurück. Wenn man nicht weiß, wonach man suchen muss, ist sie schwer zu finden, aber dieser Artikel wirkt wie eine ziemlich gute Zusammenfassung: https://plato.stanford.edu/entries/frege-logic
Das Turnstile-Symbol
|-wurde bereits verwendet, und die horizontale Linie, die im Unterricht „Fregescher Schlussstrich“ genannt wurde, also Freges Schlussstrich, scheint ursprünglich Teil des Turnstile selbst gewesen zu sein und in der modernen Notation zu einem separaten Element geworden zu sein.Benjamin C. Pierces Types and Programming Languages ist ein gutes Lehrbuch, das solche Inhalte behandelt.
Obwohl ich Informatik studiert habe, verwirren mich der Bedeutungsunterschied zwischen
|–und|=sowie die Frage, auf welcher metasyntaktischen Ebene die verwendeten Variablen jeweils liegen, immer noch.Ironischerweise liegt eine Ursache darin, dass die Notation selbst keine expliziten Typen hat.
Für alle, die überlegen, ob sie es lesen sollen: Dieser Artikel erklärt Typ-System-Notation aus Informatik-Papers und ist im Grunde eine Einführung in BNF-Notation, Inferenzregeln usw. für Typsysteme.
Wirkt wie eine gute Zusammenfassung.
Das logische Konzept der Typanwendung verstehe ich, aber da ich nicht oft Informatik-Papers lese, bleibt die Zuordnung von Symbolen und Bedeutung in meinem Kopf nicht richtig hängen.
In einem der Beispiele bedeutet
𝗍𝗋𝗎𝖾+2:𝖨𝗇𝗍, dass „𝗍𝗋𝗎𝖾+2vom Typ𝖨𝗇𝗍ist“, aber dort heißt es, das sei umso seltsamer, weil der Ausdruck𝗍𝗋𝗎𝖾+2selbst keinen Sinn ergibt und keinen Typ hat.In Python ist
True + 2allerdings tatsächlich eine ganze Zahl und hat den Wert 3. Unabhängig davon, ob das so sein sollte: Es ist tatsächlich so.True + 2Sinn ergibt, kann man einfach eine Urteilsregel definieren, die das erlaubt.Logik und Typsystemtheorie interessieren sich nicht dafür, welche Axiome und Inferenzregeln man verwendet; sie ermöglichen nur, über diese Regeln und ihre Wechselwirkungen zu schließen. Man könnte zum Beispiel
|- True : Bool,|- True : Intfestlegen oder, wenn man es nur in bestimmten Ausdrücken erlauben will, aus|- x : Intso etwas wie|- True + x: Intableiten.truezum Beispiel auf 1 abgebildet, also wirdtrue+1=2.True + 2in Python oder C keinen Fehler auslöst, ist es trotzdem töricht, weil es die Semantik der Sprache schwerer nachvollziehbar macht, nur um Programmierern ein wenig syntaktischen Zucker zu geben.Gut. Ich habe mich das seit Jahren gefragt, wusste aber nicht, welche Suchbegriffe ich verwenden sollte, um mehr darüber herauszufinden.
Manchmal ärgert es einen grundlos, wenn jemand esoterisches Wissen, das man sich mühsam angeeignet hat, kostenlos offenlegt ;) Ich wünschte wirklich, es hätte so einen Artikel gegeben, als ich das gelernt habe. Ich hoffe, dass bessere Zugänglichkeit zu weniger vermurksten Sprachen führt.
Beim Lesen des Ada Reference Manual habe ich diese Art von Syntax sofort wiedererkannt. Den Namen kannte ich nicht, aber es war interessant, sie in einem realen Anwendungsfall zu sehen; die ganze Sprache ist mit dieser Notation definiert.
Beispiel: https://ada-lang.io/docs/arm/AA-3/AA-3.7#syntax
Das Ada Reference Manual benennt die von ihm verwendete Notation ausdrücklich. Es nutzt eine Variante der Backus-Naur Form, und der verlinkte Abschnitt erklärt genau diese Variante.
Das scheint ein guter Ort zu sein, um einen Hügel zu predigen, auf dem ich bis zum Ende stehen bleiben will. Bei Typannotationsformaten, die einen Doppelpunkt verwenden, sollten die Leerzeichen auf beiden Seiten des Doppelpunkts gleich sein.
Für mich sind das zufällig gleich aussehende, also als zwei Punkte geschriebene, aber verschiedene Zeichen. Eines ist der Label-Doppelpunkt, bei dem wie im Englischen der vordere Teil den hinteren einführt oder die linke Seite das Label für die rechte ist; dazu gehören der Blockanfang in Python, Key-Value-Paare sowie Name-Wert-Paare in Structs in C oder Rust.
Das andere ist die aus der Mathematik entlehnte Typannotation. Sie ist eine binäre Relation, und binäre Relationen bekommen links und rechts gleich viel Leerraum. So wie man nicht
x= 1,x> yoderx+ zschreibt, wirktx : Xnatürlicher alsx: X.Wenn ich
a: bsehe, lese ich es sofort als Label-Doppelpunkt; wenn es eine Typannotation ist, ist jedes Mal eine sehr kleine, aber zusätzliche mentale Umwandlung nötig. Es geht hier um Syntax von Programmiersprachen, und persönlich bevorzuge ichx : Xdeutlich gegenüberX x.[1] „Evangelion“ ist ein schönes Wort, das von εὐαγγέλιον kommt, also „gute Nachricht“ bedeutet. [2] https://en.wikipedia.org/wiki/Colon_(punctuation)#Usage_in_E...
f: X->Yvor, bei der rechts vom Doppelpunkt mehr Abstand steht; eines von drei von mir geprüften Büchern verwendete ausschließlich diese Schreibweise.Außerdem ist auch das immer noch eher eine Form von Labeling, nämlich das Labeln einer bestimmten Art von Abbildung. Ein Fall, in dem der Doppelpunkt in der Mathematik wirklich in einer anderen Bedeutung verwendet wird, ist als Abkürzung für
such that, etwa in Mengendefinitionen wie{ x : x \in IN and x | 2}oder häufig zusammen mit Quantoren.X x-Notation empfinde.x: Xwirkt auf mich viel natürlicher und fühlt sich auch näher an der Verwendung des Doppelpunkts in natürlicher Sprache an.Da gibt es eine Aussage, und das, was nach dem Doppelpunkt kommt, erläutert diese Aussage genauer; da ein Typ ebenfalls zusätzliche Information über das links Stehende ist, passt das genau.
t[Leerzeichen]:[Leerzeichen]T.Die Typentheorie insgesamt ist in mancher Hinsicht ein inkonsistentes Durcheinander, aber dies ist einer der seltenen Fälle, in denen alle ziemlich konsistent sind. Ich war neugierig, wie ich das im Studium geschrieben habe, und sah nach: Auch ich hatte es schön symmetrisch notiert: https://dvt.name/logic/horse2.pdf
x: Xentspricht der Verwendung „nach dem Doppelpunkt kommt eine Erklärung“.Also etwa
Variable x: It’s an X..age: intlässt sich im Englischen leicht als „person’s age: an integer“ umformulieren.Deshalb hat mich der Doppelpunkt nie besonders gestört.