Kapitel 6: Typinferenz Andreas Abel LFE Theoretische Informatik Institut für Informatik Ludwig-Maximilians-Universität München 6. Juni 2011 Quelle: Martin Wirsing, Typüberprüfung und Typinferenz Foliensatz ProMo SS 2010 Andreas Abel (LMU) Typinferenz 06.06.11 1 / 27 Einführung Einführende Fragen Wozu dient das SML-Typsystem? Welche Vorteile haben getypte Sprachen gegenüber ungetypten? Wie bestimmt man den Typ eines SML-Programmes? Z.B. fn x => fn f => f x (fn x => x 0) 1 fn f => fn x => f x + f 1.0 fn x => x x fn x => fn f => x (f x) Inhalt: Typisierungsregeln Typsubstitutionen und Unifikation Typinferenz Andreas Abel (LMU) Typinferenz 06.06.11 2 / 27 Einführung Typfehler zur Laufzeit? Was passiert bei der Auswertung von (fn x => x 0) 1? Keine Typprüfung: 1 wird als Sprungaddresse interpretiert. Systemabsturz oder schwere Betriebssystem-Ausnahme! Dynamische Typprüfung: Werte tragen zur Laufzeit Typ-Markierung Ganzzahl, Funktion, . . . Da 1 keine Funktion ist, wird eine Ausnahme im Laufzeitsystem geworfen. Nachteil: Vor jeder Operation müssen Typen geprüft werden! Statische Typprüfung: Der Übersetzer weigert sich, ein Programm mit Typfehlern zu übersetzen. Es kommt nie zu derartigen Laufzeitfehlern. Robin Milner (1934-2010): Well-typed programs do not go wrong. Der Übersetzer findet bereits viele Programmierfehler. Andreas Abel (LMU) Typinferenz 06.06.11 3 / 27 Einführung Typsysteme in der Praxis Keine Typprüfung: Assembler Dynamische Typprüfung: LISP, Scheme, BASIC Skriptsprachen: JavaScript, Python Statische Typprüfung: C (unsicher), JAVA (teilw. dynamisch), Scala, SML, Ocaml, Haskell, Agda. Erfahrung mit statisch getypten funktionalen Sprachen: If it type-checks, it works. Andreas Abel (LMU) Typinferenz 06.06.11 4 / 27 Ausdrücke Ausdrücke (abstrakte Syntax) Wir betrachten Programme e im funktionalen Kern von SML, dem λ-Kalkül (1936, Alonzo Church(1902-95)): e ::= | | | c x e e0 fn x ⇒ e Konstante Variable Anwendung Funktionsabstraktion 2.718 xdiff Math.sqrt 5.0 fn xd => xd - 1 Eine Konstante c ist z.B. eine Ganz- oder Fließkommazahl, eine Zeichenkette, oder eine Operation. c ::= | | | n r s f Ganzzahl Fließkommazahl Zeichenkette Operation 0, 1, ~1 0.01, 2.0 "Anton", "" +, -, o, @ Infix-Operatoren 5 + 3 schreiben wir präfix + 5 3 in abstrakter Syntax. Andreas Abel (LMU) Typinferenz 06.06.11 5 / 27 Ausdrücke Typausdrücke und Typisierungskontexte Wir beschränken uns auf Grund- und Funktionstypen. Grammatik für die abstrakte Syntax von Typen: A, B ::= α | β | . . . | int | real | string | . . . | A→B Typvariable ’a, ’b Grundtyp Funktionstyp int -> ’a Klammern sind in der Grammatik nicht erwähnt, sie dienen nur der Disambiguierung. A → B → C = A → (B → C ) 6= (A → B) → C Andreas Abel (LMU) Typinferenz 06.06.11 6 / 27 Typisierung Typzuweisung Eine Typzuweisung e : A ist ein Paar aus einem SML-Ausdruck e und einen SML-Typen A, interpretiert als Aussage “e hat Typ A”. 4 true 3.3 fn x fn x fn x z (x : : : => x - 1 : => fn y => x : => fn y => y : + 0) : int wahr string falsch ’a falsch int -> int wahr ’a -> ’b -> ’a wahr ’a -> ’b -> ’a falsch bool ??? Der Typausdruck z (x + 0) enthält freie Variablen x und z. Wir benötigen eine Typzuweisung für die freien Variablen! Andreas Abel (LMU) Typinferenz 06.06.11 7 / 27 Typisierung Typisierungskontext Eine endliche Menge Γ = {x1 :A1 , . . . xn :An } (n ≥ 0) von Typzuweisungen an paarweise verschiedene Variablen x1 , . . . , xn heißt Typisierungskontext (engl. typing context/environment). {b : bool, half : int → int} {z : int → bool, x : int} {h : int → bool, h : int} gültig gültig ungültig Γ ist eine endliche Abbildung von Variablen x auf Typen Γ(x). Wir schreiben Γ, x:A für das Einfügen der Typzuweisung x:A in Γ. Eine schon vorhandene Typzuweisung für x wird überschrieben. Bsp.: {h : int → bool}, h : int = {h : int}. Andreas Abel (LMU) Typinferenz 06.06.11 8 / 27 Typisierung Typisierung Ein Typ(isierungs)urteil (engl: typing judgement) ist eine Aussage der Form Γ ` e : A . Bedeutung: “Unter der Annahme, dass die Variablen die in Γ angegebenen Typen haben, hat e den Typ A”. Kurz: “Im Kontext Γ hat e den Typ A”. Formal ist ` : eine dreistellige Relation zwischen Typkontexten, Ausdrücken, und Typen. {y : int} ` fn x ⇒ + y (* 2 x) : int → int gültig {x : ’a} ` fn y ⇒ x : ’b → ’a gültig {} ` fn x ⇒ + x x : ’a → ’a ungültig Andreas Abel (LMU) Typinferenz 06.06.11 9 / 27 Typisierung Typregeln Gültige Typurteile werden mittels Typregeln hergeleitet. Eine Regel hat die Form P1 ∧ · · · ∧ Pn =⇒ K , d.h., aus den Prämissen P1 . . . Pn folgt die Konklusion K , geschrieben P1 . . . Pn K In unserem Fall sind die Pi und K Typurteile. Regeln ohne Prämissen heißen Axiome. Für jede Konstante c gibt es ein Typaxiom, z.B. Γ `1 : int Γ ` true : bool Γ `+ : int → int → int Γ ist jeweils beliebig, z.B. Γ = {} oder Γ = {x : int, b : bool}. Γ ` 1 : int ist eigentlich ein Axiomenschema, denn für jedes Γ gibt es ein Axiom. Andreas Abel (LMU) Typinferenz 06.06.11 10 / 27 Typisierung Typisierung von Variablen Der Typ einer Variablen ist im Kontext notiert: (x : A) ∈ Γ var Γ `x :A Äquivalent: Γ ` x : Γ(x) var Beispiele: {x : real} {x : real, y : int, z : bool} {x : real, y : int, z : bool} {} Andreas Abel (LMU) ` ` ` ` x y y x Typinferenz : real : int : real : real gültig gültig falscher Typ ungebundene Variable 06.06.11 11 / 27 Typisierung Typisierung der Anwendung Der Typ der Anwendung einer Funktion e vom Typ A → B auf ein Argument e 0 vom Typ A ist B. Γ `e:A→B Γ ` e0 : A app Γ ` e e0 : B Falls e keinen Funktionstyp hat oder e 0 nicht im Definitionsbereich (engl. domain) A liegt, ist die Anwendung nicht wohlgetypt. Beispiel: Γ = {a : int, y : int} Γ ` fn x ⇒ + a x : int → int Γ ` * 2 y : int app Γ ` (fn x ⇒ + a x) (* 2 y) : int Andreas Abel (LMU) Typinferenz 06.06.11 12 / 27 Typisierung Typisierung der Abstraktion Hat e den Typ B unter der Annahme x : A, so hat fn x ⇒ e den Typ A → B. Γ, x : A ` e : B abs Γ ` fn x ⇒ e : A → B Herleitungsbaum (Bsp.): {x : α, f : α → β} ` f : α → β var {x : α, f : α → β} ` x : α app {x : α, f : α → β} ` f x : β {x : α} ` fn f ⇒ f x : (α → β) → β abs {} ` fn x ⇒ fn f ⇒ f x : α → (α → β) → β Andreas Abel (LMU) Typinferenz var abs 06.06.11 13 / 27 Typisierung Herleitung Eine Herleitung ist eine Folge von (Typ)aussagen J1 , . . . , Jn , so dass jedes Ji entweder ein Axiom ist, oder mit Konklusion einer Regel mit Prämissen in J1 , . . . , Ji−1 . Die hergeleitete Aussage ist die letzte, Jn . Herleitung in linearer Form: 1 2 3 4 5 {x {x {x {x {} : α, f : α → β} ` f : α → β : α, f : α → β} ` x : α : α, f : α → β} ` f x : β : α} ` fn f ⇒ f x : (α → β) → β ` fn x ⇒ fn f ⇒ f x : α → (α → β) → β var var app(1, 2) abs(3) abs(4) Eine Aussage ist herleitbar, wenn sie eine Herleitung hat. Andreas Abel (LMU) Typinferenz 06.06.11 14 / 27 Typisierung Kontexterweiterung Eine Typaussage bleibt gültig, wenn wir den Typisierungkontext erweitern. Bsp: {x : α} {x : α, y : γ} {f : int → bool, x : α, y : γ} ` fn f ⇒ f x : (α → β) → β ` fn f ⇒ f x : (α → β) → β ` fn f ⇒ f x : (α → β) → β Lemma (Kontexterweiterung/Abschwächung (engl. weakening)) Wenn Γ0 ⊇ Γ und Γ ` e : A herleitbar ist, dann auch Γ0 ` e : A. Beweisbar durch Induktion über die Länge der Herleitung J1 , . . . , Jn , Γ ` e : A. Die folgende Regel ist also zulässig (engl. admissible): Γ0 ⊇ Γ Γ `e:A weak 0 Γ `e:A Andreas Abel (LMU) Typinferenz 06.06.11 15 / 27 Polymorphie und Substitutionen Polymorphie Viele Ausdrücke haben mehrere Typisierungen. Z.B. f x {x {x {x {x : int, f : int → int} : bool, f : bool → int} : α, f : α → α} : α, f : α → β} ` ` ` ` f f f f x x x x : : : : int int α β Die letzte dieser Typisierungen ist die allgemeinste (engl. principal typing); jede andere Typisierung von f x erhält man daraus durch Einsetzen von Typen für die Typvariablen α und β (Instanziierung). {x : α, f : α → β}[bool/α, int/β] ` f x : β[bool/α, int/β] Die allgemeinste Typisierung ist bis auf Typvariablen-Umbenennung eindeutig. Andreas Abel (LMU) Typinferenz 06.06.11 16 / 27 Polymorphie und Substitutionen Typsubstitutionen Eine Typsubstitution σ ist eine endliche Abbildung von Typvariablen α auf Typausdrücke A. σ1 = = = σ2 = σ3 = σ4 = [bool/α, int/β] [bool/α, int/β, γ/γ] [bool/α, int/β, γ/γ, δ/δ] . . . [β/α, (β → bool)/β, int/γ] [γ/α, δ/β] [β/α, α/β] Für die Anwendung Aσ einer Substitution σ auf Typen A gilt z.B.: int σ = int (A → B)σ = Aσ → Bσ α[bool/α, int/β] = bool Andreas Abel (LMU) Typinferenz 06.06.11 17 / 27 Polymorphie und Substitutionen Instanziierung Die Anwendung Γσ einer Substitution σ auf einen Typkontext Γ ist punktweise definiert: (Γσ)(x) = (Γ(x))σ. {x : int, y : α → β, z : α}[γ/α, int/β, bool/γ] = {x : int, y : γ → int, z : γ} Alle Typaxiome und -regeln bleiben unter Substitution gültig. Lemma (Typerhaltung unter Substitution) Wenn Γ ` e : A, dann Γσ ` e : Aσ. Zulässige Regel: Γ `e:A subst Γσ ` e : Aσ Andreas Abel (LMU) Typinferenz 06.06.11 18 / 27 Polymorphie und Substitutionen Substitutionskomposition Die Komposition σσ 0 ist definiert durch A(σσ 0 ) = (Aσ)σ 0 . Berechnung der Komposition zweier Substitutionen: α[(α → β)/α][int/α, bool/β] = (α → β)[int/α, bool/β] = int → bool β[(α → β)/α][int/α, bool/β] = β[int/α, bool/β] = bool [(α → β)/α][int/α, bool/β] = [(int → bool)/α, bool/β] Komposition ist assoziativ, σ(σ 0 σ 00 ) = (σσ 0 )σ 00 , aber nicht kommutativ. Die Identitätssubstitution id = [] ist neutrales Element der Komposition, es gilt id σ = σ und σ id = σ. Andreas Abel (LMU) Typinferenz 06.06.11 19 / 27 Polymorphie und Substitutionen Prinzipale Typisierung Eine Typisierung Γ ` e : A eines Ausdrucks e heißt prinzipal oder allgemeinstmöglich, falls für jede weitere Typisierung Γ0 ` e : A0 von e eine Substitution σ gibt, so dass A0 = Aσ und Γ0 ⊇ Γσ. {x {x {x {x : α, f : α → β} : int, f : int → int} : α, f : α → β, y : γ} : γ, f : γ → β} ` ` ` ` f f f f x x x x : : : : β prinzipal int σ = [int/α, int/β] β σ = id β σ = [γ/α] (auch prinzipal) Überladene Operatoren haben keine allgemeinste Typisierung. + : int → int → int + : real → real → real Ohne Überladung hat jeder SML-Ausdruck eine prinzipale Typisierung. Andreas Abel (LMU) Typinferenz 06.06.11 20 / 27 Typinferenz Typinferenz Typinferenz berechnet zu jedem Ausdruck den allgemeinsten Typ (Fehlermeldung, falls der Ausdruck keinen Typ hat). Der prinzipale Typ einer Applikation e e 0 muss aus den prinzipalen Typen von e und e 0 berechnet werden. {} ` e : (int → β) → (γ → β) {x : α} ` e 0 : α → real {x : int} ` e e 0 : γ → real Beide Prämissen müssen mit [int/α, real/β] instanziiert werden. Allgemeinste Lösung der Gleichung int → β = α → real. {f : α} ` f : α {x : β} ` x : β {f : β → γ, x : β} ` f x : γ Löse Gleichung α = β → γ für neue Typvariable γ. Andreas Abel (LMU) Typinferenz 06.06.11 21 / 27 Typinferenz Unifikation Eine Substitution σ ist allgemeiner als σ 0 , falls es eine Substitution τ gibt mit σ 0 = στ . Die speziellere Substitution σ 0 ist also eine Instanz von σ. Ein Unifikator zweier Typen A und A0 ist eine Substitution σ, so dass Aσ = A0 σ. (α → β)[int/α, int/β] = (int → β)[int/α, int/β] Der allgemeinste Unifikator von A und A0 ist die allgemeinste Substitution σ, so dass Aσ = A0 σ. (α → β)[int/α] = (int → β)[int/α] Andreas Abel (LMU) Typinferenz 06.06.11 22 / 27 Typinferenz Robinson’s Unifikationsalgorithmus Der Unifikationsalgorithmus unify arbeitet auf einer Menge E = {A1 = B1 , . . . , An = Bn } von formalen Typgleichungen und liefert die allgemeinste Substitution σ, so dass A1 σ = B1 σ, . . . , An σ = Bn σ, oder einen Typfehler. 1 unify({}) = id (* Fertig! *) 2 unify({A = A} ] E ) = unify(E ) (* redundante Gleichung *) 3 unify({A → A0 = B → B 0 } ] E ) = unify({A = B, A0 = B 0 } ] E ) 4 unify({α = B} ] E ) = unify({B = α} ] E ) = if α occurs in B then “Error: circularity” else let σ = [B/α] in σ unify(E σ) (* Substitutionskomposition! *) 5 Sonst unify(E ) = “Error: type mismatch” (*z.B. (int = bool) ∈ E *) Andreas Abel (LMU) Typinferenz 06.06.11 23 / 27 Typinferenz Beispiele Unifikation Nur Variablen: unify{α = β, β = γ} = [β/α]unify{β = γ} = [β/α][γ/β]unify{} = [β/α][γ/β]id = [β/α][γ/β] = [γ/α, γ/β] Typkonstruktorkonflikt: unify{int → α = α → real} = unify{int = α, α = real} = [int/α]unify{int = real} = ”Error: type mismatch” Zirkulärer Typ: unify{β = β, α = α → real} = unify{α = α → real} = ”Error: circularity” Andreas Abel (LMU) Typinferenz 06.06.11 24 / 27 Typinferenz Typinferenzalgorithmus inferΓ (e) = (A, σ) nimmt einen Ausdruck e in Typkontext Γ und liefert den allgemeinsten Typ A von e samt der allgemeinsten Instanziierung σ von Γ, so dass Γσ ` e : A. 1 inferΓ (x) = if (x : A) ∈ Γ then (A, id) else “Error: unbound variable” 2 inferΓ (c) = let A = type of constant c in (A, id) 3 inferΓ (fn x ⇒ e) = let α new (previously unused) type variable let (B, σ) = inferΓ,x:α (e) (* hence (Γσ, x : ασ) ` e : B *) in (ασ → B, σ) (* thus, Γσ ` fn x ⇒ e : ασ → B *) Andreas Abel (LMU) Typinferenz 06.06.11 25 / 27 Typinferenz Typinferenzalgorithmus (Applikation) inferΓ (e) = (A, σ) nimmt einen Ausdruck e in Typkontext Γ und liefert den allgemeinsten Typ A von e samt der allgemeinsten Instanziierung σ von Γ, so dass Γσ ` e : A. 4 inferΓ (e e 0 ) = let (C , σ) = inferΓ (e) (* Γσ ` e : C *) (* Γσσ 0 ` e : C σ 0 *) let (A, σ 0 ) = inferΓσ (e 0 ) (* Γσσ 0 ` e 0 : A *) let σ 00 = unify{C σ 0 = A → β} for new β (* C σ 0 σ 00 = Aσ 00 → βσ 00 *) in (βσ 00 , σσ 0 σ 00 ) (* Γσσ 0 σ 00 ` e e 0 : βσ 00 *) Dieser Algorithmus heißt W und geht auf Roger Hindley und Robin Milner zurück. Andreas Abel (LMU) Typinferenz 06.06.11 26 / 27 Zusammenfassung Zusammenfassung Well-typed programs do not go wrong (Robin Milner) Typisierung Γ ` e : A. Typaxiome, Typregeln, Typherleitung. Polymorphie und allgemeinste (prinzipale) Typen. SML-Typinferenz berechnet zu jedem Ausdruck prinzipalen Typ. Typinferenz beruht auf Unifikation (Robinson). Andreas Abel (LMU) Typinferenz 06.06.11 27 / 27