13 Typinferenz 13 · Typinferenz 13.1 � Typsysteme · 13.1 Typsysteme Ziel der Typprüfung: alle Operanden von Operatoren haben einen kompatiblen Typ. • ... “Operatoren” sehr allgemein verstanden (z.B. auch Zuweisungen, Funktionsaufrufe, ...) � Kompatibler Typ: Vom Operator erwarteter Typ. • Manche Sprachen erlauben auch Typen, deren Werte automatisch in den erwarteten Typ konvertiert werden können. • Diese automatische Typkonvertierung nennt man Coercion, die zugehörigen Regeln sind oft komplex50 . � Ein Typfehler besteht dann in der Anwendung eines Operators auf einen Wert inkompatiblen Typs. 50 https://www.destroyallsoftware.com/talks/wat Stefan Klinger · DBIS Informatik 2 · Sommer 2016 401 13 · Typinferenz Geschmacksrichtungen Typsysteme · 13.1 tatsächlich ist diese Einteilung etwas schwammig static/dynamic typing Ein statisches Typsystem überprüft die Typkorrektheit beim Kompilieren (Haskell, Java, C). • Zur Laufzeit liegen evtl. gar keine Typinformationen mehr vor (C). • Ein dynamisches Typsystem prüft erst zur Laufzeit (Python). strong/weak typing Ein starkes Typsystem erkennt alle Typfehler, ggf. aber erst zur Laufzeit. • Ein schwaches Typsystem kann bei unpassenden Typen zu unerwartetem Verhalten führen (C), oder das Programm abbrechen. polymorphic typing Polymorphie erlaubt die Verwendung einer Funktion mit verschiedenen aber passenden Typen. • Parametrische Polymorphie — Funktionen werden ohne Annahme über den Typ des Argumentes implementiert. • Ad-hoc Polymorphie — Für den jeweiligen Typ wird die passende Implementierung einer Funktion ausgewählt. • Subtyping — Funktionen für einen bestimmten Typ (z.B. Mammal) können auch Werte von Untertypen (z.B. Bunny) verarbeiten. Insgesamt sind Bezeichnungen wie statisch/dynamisch oder stark/schwach eher als extreme Pole eines Spektrums von möglichen Ausprägungen zu verstehen. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 402 13 · Typinferenz Typsysteme · 13.1 Programmierer profitieren von der Typprüfung ihrer Programme: � “Well-typed programs do not ‘go wrong’.” (Robin Milner): ein typkorrektes Programm wendet Funktionen nur auf Werte an, für die sie auch definiert wurden. � Viele ansonsten schwer zu entdeckende Fehler werden durch den Type-Checker zur Compile-Zeit erkannt. 1 Prelude> foldl sqrt 2 3 4 <interactive>:2:7: Occurs check: cannot construct the infinite type: b ~ a -> b Aber auch der Compiler und die Laufzeitumgebung ziehen Vorteile aus der Typisierung: � Für ein typkorrektes Programm muss das Laufzeitsystem der Sprache keine Tests auf die Typkorrektheit ausführen (⇒ kürzere Laufzeiten). � Der Compiler muss entsprechend keinen Code für solche Tests generieren (⇒ kompakterer Objekt-Code). Stefan Klinger · DBIS Informatik 2 · Sommer 2016 403 13 · Typinferenz Typsysteme · 13.1 Überprüfung der Typkorrektheit � Type Checking • Programmierer deklariert zu jedem Objekt (Variablen, Parameter, ...) den gewünschten Typ. • Compiler (statisch) bzw. Laufzeitsystem (dynamisch) prüft Typregeln und meldet ggf. Typfehler. � Type Inference • Programmierer gibt (fast) keine explizite Typisierung von Objekten an. • Compiler leitet gewünschten Operandentyp aus Operatoren und Inferenzregeln ab, meldet ggf. Typfehler. Die meisten Programmiersprachen verwenden teilweise Typinferenz (z.B. bei numerischen Konstanten), fordern aber auch Typdeklarationen, die dann (statisch/dynamisch) geprüft werden. Haskell basiert (fast ausschließlich) auf Typinferenz. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 404 13 · Typinferenz 13.2 � � Automatische Typinferenz · 13.2 Automatische Typinferenz In Haskell ist es bis auf wenige Ausnahmefälle nicht notwendig (aber sehr hilfreich), Werte und Ausdrücke mit ihren jeweiligen Typen zu annotieren. Stattdessen leitet der Compiler die Typisierung automatisch ab. Die Typinferenz-Komponente eines Compilers 1. prüft, ob ein Programm nach den Typregeln der Sprache korrekt typisiert ist (type check), und 1 erfüllt sein) leitet automatisch den Typ jedes Ausdrucks innerhalb 2. (sollte ○ dieses Programms ab (type inference). � Diese beiden Aufgaben werden verschränkt (nicht eine nach der anderen) ausgeführt. � 1 und Mittels Intuition und “scharfem Hinsehen” lassen sich die Punkte ○ ○ 2 für viele Ausdrücke und Funktionsdefinitionen auch intuitiv ableiten. Der später vorgestellte Inferenzalgorithmus orientiert sich an dieser Intuition. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 405 13 · Typinferenz Beispiel Automatische Typinferenz · 13.2 Typinferenz für die Funktion foldr mit folgender Definition: foldr f z = go where go [ ] = z go (x : xs) = f x (go xs) “Scharfes Hinsehen” liefert: 1. go operiert offensichtlich auf Listen und hat daher den Typ [α] → β. 2. Sowohl z als auch f x (go xs) können ein Ergebnis von go darstellen, und haben also den Typ β. 3. Da x :: α und (go xs) :: β, muss f vom Typ α -> β -> β sein. Also: foldr :: (α → β → β) → β → [α] → β Stefan Klinger · DBIS Informatik 2 · Sommer 2016 406 13 · Typinferenz Automatische Typinferenz · 13.2 Beobachtungen Diese intuitive Typinferenz umfasst dabei die 1. Bestimmung eines Typs für einen Ausdruck an sich, wie etwa im letzten Beispiel für go. Im Allgemeinen wird dieser Typausdruck Typvariablen beinhalten. 2. Bestimmung eines Typs für einen Teilausdruck bereits typisierter Ausdrücke, wie eben für z und f geschehen. Typkorrekt Soll der Gesamtausdruck typkorrekt sein, dürfen sich die dabei abgeleiteten Typen nicht widersprechen, d.h. sie müssen durch geeignete Substitution von Typvariablen in dieselbe Form gebracht werden können. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 407 13 · Typinferenz Automatische Typinferenz · 13.2 Kernsprache für Typinferenz Um den Rahmen der Betrachtungen nicht zu sprengen, besprechen wir hier einen Typinferenz-Algorithmus für einen erweiterten λ-Kalkül: � Wir erweitern den einfachen λ-Kalkül um Konstanten (cf. Seite 94) und lokale Definitionen via let ... in , und erhalten Expr → | | | | Const Var Expr Expr λVar. Expr let Var = Expr in Expr Konstanten Variablen Applikation λ-Abstraktion lokale Definition • Der let-Ausdruck kann hier tatsächlich nur eine Variable binden, erlaubt uns aber immerhin Rekursion einzuführen. • Üblicherweise wird ein mächtigeres let verwendet, das z.B. auch wechselseitig rekursive Definitionen zulässt. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 408 13 · Typinferenz 13.3 � Inferenzregeln · 13.3 Inferenzregeln Inferenzregeln werden in der Logik oft in der Form Prämisse1 Prämisse2 Prämisse3 Folgerung verwendete Regel notiert. • Auf dieser Notation bauen z.B. Systeme natürlichen Schließens auf, eine Familie von Kalkuli aus der Logik. � Damit lassen sich ganze Beweise elegant als Inferenzbäume aufschreiben51 : X ⇒ Y X Y ⇒B Y ∧Z � Z ∧E Angelehnt an diese Notation werden wir die Regeln der Typinferenz notieren. 51 Dabei stehen B bzw. E für Beseitigungs- bzw. Einfügeregeln. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 409 13 · Typinferenz 13.4 Regeln für die Typinferenz · 13.4 Regeln für die Typinferenz Applikation & Abstraktion � In diesem Kapitel stehen die Ti für noch unbekannte Typen (i ∈ N). Die Typvariablen (α, β, ...) verwenden wir dann für polymorphe Typen. � Seien f , e beliebige λ-Ausdrücke; x eine Variable. Applikation Die Inferenzregel für die Funktionsapplikation f e lautet: f :: T1 → T2 e :: T1 f e :: T2 @ � In einer Applikation f e muss f einen Typ haben, der den Typ T1 des Arguments auf einen Ergebnistyp T2 abbildet. � T2 ist dann der Typ des Ausdrucks f e. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 410 13 · Typinferenz Regeln für die Typinferenz · 13.4 Abstraktion Für die Abstraktion λx. e lautet die Regel: e :: T2 λx. e :: T1 → T2 λx :: T1 � Unter der Annahme52 daß die alle in e freien x den Typ T1 haben, ist e :: T2 . � Die Funktion λx. e liefert dann für ein Argument vom Typ T1 Werte vom Typ T2 des Funktionsrumpfes e. 52 diese notieren wir etwas unorthodox rechts neben dem Bruchstrich. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 411 13 · Typinferenz Regeln für die Typinferenz · 13.4 Beispiel � Bestimme den Typ T0 von const = λx. λy . x: Für eine Abstraktion λx... verwenden wir immer die λ-Regel: λy . x :: T2 λx. λy . x :: T0 • Daraus lernen wir schon: λx :: T1 T 0 = T1 → T 2 , • allerdings bleibt die Frage was T2 für ein Typ sein soll. � Auch dafür wenden wir die λ-Regel an, und schreiben sie darüber: x :: T1 • Dabei wissen wir schon aus der λy :: T3 unteren Regel, daß x den Typ T1 λy . x :: T2 haben muss! λx :: T1 λx. λy . x :: T0 • Neu lernen wir T = T → T . 2 � 3 1 Einsetzen der Gleichung für T2 in die Gleichung für T0 liefert: T 0 = T1 → T 3 → T 1 � Diese Erkenntnis gilt offenbar für alle Typen T1 und T3 : const :: ∀α β. α → β → α Stefan Klinger · DBIS Informatik 2 · Sommer 2016 412 13 · Typinferenz Regeln für die Typinferenz · 13.4 Notation � Es ist nicht nötig die einzelnen Teilausdrücke bei jedem Schritt hinzuschreiben: x :: T1 T2 T0 λy :: T3 λx :: T1 x :: T1 statt λy . x :: T2 λy :: T3 λx. λy . x :: T0 λx :: T1 • An der Struktur des Inferenzbaumes lässt sich die Struktur des Ausdrucks ablesen — es ist der AST mit der Wurzel unten. � Analog zu λx y z. e = λx. λy . λz. e fassen wir Abstraktionen oft zusammen: Für λx y . x also x :: T1 λy :: T3 x :: T1 statt λx :: T1 , y :: T2 T2 T0 λx :: T1 T0 • Daraus lesen wir direkt ab: T0 = T1 → T2 → T1 . • Offensichtlich sparen wir uns eine Typvariable — und auch Rechenarbeit. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 413 13 · Typinferenz Beispiel Regeln für die Typinferenz · 13.4 Typisierung von λx f . f x: 1. Zuerst konstruieren wir den Inferenzbaum: f :: T2 x :: T1 T3 • � T0 (rechts der AST zum Vergleich) x f @ @ λx :: T1 , f :: T2 λf λx Wichtig: An alle durch das gleiche Lambda gebundenen Variablen die gleiche Typvariable schreiben! 2. Dann fangen wir unten (bei der Wurzel T0 ) an, Gleichungen abzulesen: T 0 = T 1 → T2 → T3 T 2 = T 1 → T3 aus der λ-Regel aus der @-Regel 3. Einsetzen in T0 liefert: T0 = T1 → (T1 → T3 ) → T3 . 4. Allquantifizieren liefert: λx f . f x :: ∀α β. α → (α → β) → β. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 414 13 · Typinferenz Regeln für die Typinferenz · 13.4 Beispiel Die Typisierung von S = λf g x. f x (g x): Wir konstruieren den Inferenzbaum (rechts nochmal der AST zum Vergleich) � f :: T1 x :: T3 T5 @ T4 T0 � lesen ab � setzen ein � g :: T2 T6 x :: T3 g x f x @ @ @ @ @ λf :: T1 , g :: T2 , x :: T3 λf g x T0 = T 1 T5 = T 6 T1 = T 3 T2 = T 3 → T2 → T3 → T 4 → T4 → T5 → T6 T0 = (T3 → T5 ) → (T3 → T6 ) → T3 → T4 = (T3 → T6 → T4 ) → (T3 → T6 ) → T3 → T4 und allquantifizieren: Stefan Klinger · DBIS S :: ∀α β γ. (α → β → γ) → (α → β) → α → γ Informatik 2 · Sommer 2016 415 13 · Typinferenz Beispiel Regeln für die Typinferenz · 13.4 Innere Abstraktion und Namensüberdeckung: λx y . x (λx. x y ) x :: T5 y :: T2 T6 x :: T1 � � T4 T3 T0 @ λx :: T5 @ liefert λx :: T1 , y :: T2 T0 = T 1 T1 = T 4 T4 = T 5 T5 = T 2 → T2 → T3 → T3 → T6 → T6 Wo genau kommen die Typen Ti der einzelnen Variablen her? Dann nur noch einsetzen und allquantifizieren: T0 = ... = (((T2 → T6 ) → T6 ) → T3 ) → T2 → T3 λx y . x (λx. x y ) :: ∀α β γ. (((α → β) → β) → γ) → α → γ 1 2 3 Prelude> :t \x y -> x (\x -> x y) — Man kann ja mal fragen \x y -> x (\x -> x y) :: (((r2 -> r1) -> r1) -> r) -> r2 -> r — Eigentlich: ∀r r1 r2...., der GHC lässt den Allquantor leider weg. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 416