Haskell, Typen, und Typberechnung Grundlagen der Programmierung 3 A Ziele: Typen, Typberechnung und Typcheck Prof. Dr. Manfred Schmidt-Schauß • Haskells Typisierung • Typisierungs-Regeln • Typ-Berechnung • Milners Typcheck (Robin Milner) Sommersemester 2017 Grundlagen der Programmierung 2 (Typen-A) 1 Einige andere Programmiersprachen Typisierung in Haskell ab Java 5: generische Typen verwandt mit polymorphen Typen Haskell hat eine starke, statische Typisierung. mit parametrisch polymorphen Typen. ML (und CAML, OCAML) hat parametrisch polymorphes Typsystem • jeder Ausdruck muss einen Typ haben JavaScript Nachteile: Es fehlt eine statische Typisierung • Der Typchecker berechnet Typen aller Ausdrücke und prüft Typen zur Compilezeit • Es gibt keine Typfehler zur Laufzeit d.h. kein dynamischer Typcheck nötig. TypeScript (Microsoft): Neue JavaScript Variante mit statischer Typisierung und Typinferenz. Google Ankündigung: Angular 2 (JavaScript-Webframework) zukünftig auf Basis von TypeScript Grundlagen der Programmierung 2 (Typen-A) – 2/37 – – 3/37 – Grundlagen der Programmierung 2 (Typen-A) – 4/37 – Typisierung, Begriffe Überladung und Konversion in Haskell Haskell: polymorph: keine Typkonversion Es gibt Überladung: z.B. arithmetische Operatoren: +, −.∗, / parametrisch Die (i.a. unendliche) Menge der Typen polymorph: entspricht einem schematischen Typausdruck Zahlkonstanten für ganze Zahlen sind überladen Beispiel Typcheck erstmal ohne Überladung ohne Kinds und ohne Typen höherer Ordnung Grundlagen der Programmierung 2 (Typen-A) Ein Ausdruck kann viele Typen haben (vielgestaltig, polymorph). – 5/37 – Syntax von Typen in Haskell (ohne Typklassen) length Schema: [a]->Int Instanzen: [Int]->Int, usw. [Char]->Int, [[Char]]->Int Grundlagen der Programmierung 2 (Typen-A) – 6/37 – Syntax von Typen Typkonstruktoren können benutzerdefiniert sein (z.B. Baum a) hTypi ::= hBasistypi | (hTypi) | hTypvariablei Vordefinierte Typkonstruktoren: | hTypkonstruktorn i{hTypi}n (n = Stelligkeit) hBasistypi ::= Int | Integer | Float | Rational | Char | . . . Grundlagen der Programmierung 2 (Typen-A) – 7/37 – Liste Tupel Funktionen [·] (.,...,.) · →· (Stelligkeit 2, Infix) Konvention zu Funktionstypen mit → a → b → c → d bedeutet: a → (b → (c → d)). Grundlagen der Programmierung 2 (Typen-A) – 8/37 – Interpretation der Typen Komposition Beispiel: Komposition von Funktionen: komp::(a -> b) -> (c -> a) -> c -> b komp f g x = f (g x) length :: [a] -> Int Interpretation: Für alle Typen typ ist length eine Funktion, die vom Typ [typ] → Int ist. In Haskell ist komp vordefiniert und wird als .“ geschrieben: ” Beispielaufruf: *Main> suche_nullstelle (sin . quadrat) 1 4 0.00000001 ←- 1.772453852929175 (sin . quadrat) entspricht sin(x2 ) und quadrat . sin entspricht (sin(x))2 . Grundlagen der Programmierung 2 (Typen-A) – 9/37 – Typ der Komposition (c->a) -> Typ Typ Typ Typ c->b (τ3 -> τ1 ) τ3 τ2 (τ3 -> τ2 ) x :: τ3 Grundlagen der Programmierung 2 (Typen-A) g – 10/37 – Typen von Konstruktoren Erklärung zum Typ von komp, wobei {a,b,c} Typvariablen sind. Ausdruck: f ‘komp‘ g bzw. f . g (a->b) -> (τ1 -> τ2 ) Grundlagen der Programmierung 2 (Typen-A) / τ1 von (.) von f von g des Arguments x der Komposition f . g Typ des Resultats der Komposition f (g x) Typ von (f . g) f / Typen von Konstruktoren werden durch deren data-Definition automatisch festgelegt! data Baum a b = Empty | Blatt b | Knoten a (Baum a b) Baum a b) Typen der Empty Blatt Knoten Konstruktoren: :: Baum a b :: b → Baum a b :: b → Baum a b → Baum a b → Baum a b τ2 – 11/37 – Grundlagen der Programmierung 2 (Typen-A) – 12/37 – Typregeln Typregel Anwendung“: mehrere Argumente ” Wie berechnet man Typen von Ausdrücken? Anwendung von Funktionsausdruck auf Argument s :: σ1 → σ2 . . . → σn → τ , t1 :: σ1 , . . . , tn :: σn (s t1 . . . tn ) :: τ s :: σ → τ , t :: σ (s t) :: ? Beispiele Beispiele: quadrat :: Int → Int , (quadrat 2) :: ? + :: Int → (Int → Int) (1 +) :: ? + :: Int → Int → Int, 1 :: Int , 2 :: Int (+ 1 2) :: Int 2 :: Int , + :: Int → Int → Int (1 + 2) :: Int 1 :: Int , 2:: Int ((1 +) 2) :: ? Grundlagen der Programmierung 2 (Typen-A) – 13/37 – Beispiel (c -> a) -> quadrat::Int -> Int c -> b . a -> b = Int -> Bool . . c = Int; b = Bool . c -> a = Int -> Int Ziel: Anwendung der Typregel für z.B. length oder map Neuer Begriff: γ ist eine Typsubstitution wenn sie Typen für Typvariablen einsetzt γ(τ ) nennt man Instanz von τ Beispiel Typsubstitution: Instanz: even . quadrat :: Int -> Bool Grundlagen der Programmierung 2 (Typen-A) – 14/37 – Anwendungsregel für polymorphe Typen even::Int -> Bool (.)::(a -> b) -> Grundlagen der Programmierung 2 (Typen-A) – 15/37 – Grundlagen der Programmierung 2 (Typen-A) γ = {a 7→ Char, b 7→ Float} γ([a] → Int) = [Char] → Int – 16/37 – Anwendungsregel für polymorphe Typen Beispiel zur polymorphen Anwendungsregel Typ von (map quadrat) ? s :: σ → τ, t :: ρ und γ(σ) = γ(ρ) (s t) :: γ(τ ) map :: (a → b) → ([a] → [b]) quadrat :: Int → Int Berechnet den Typ von (s t) wenn die Typen von s, t schon bekannt sind Instanziiere mit: ergibt: Hierbei ist zu beachten: Damit alle Freiheiten erhalten bleiben: Regelanwendung ergibt: die Typvariablen in ρ müssen vorher umbenannt werden, so dass σ und ρ keine gemeinsamen Typvariablen haben. Grundlagen der Programmierung 2 (Typen-A) map :: (Int → Int) → ([Int] → [Int]), quadrat :: (Int → Int) (map quadrat) :: [Int] → [Int] – 17/37 – Polymorphe Regel für n Argumente Grundlagen der Programmierung 2 (Typen-A) – 18/37 – Wie berechnet man die Typsubstitutionen? s :: σ1 → σ2 . . . → σn → τ, t1 :: ρ1 , . . . , tn :: ρn und ∀i : γ(σi ) = γ(ρi ) (s t1 . . . tn ) :: γ(τ ) Die Typvariablen in ρ1 , . . . , ρn müssen vorher umbenannt werden. Beachte: γ = {a 7→ Int, b 7→ Int} map :: (Int → Int) → ([Int] → [Int]) Unifikation: Berechnung der allgemeinsten Typsubstitution im Typberechnungsprogramm bzw Typchecker Unifikation wird benutzt im Typchecker von Haskell! verwende möglichst allgemeines γ (kann berechnet werden; s.u.) Grundlagen der Programmierung 2 (Typen-A) – 19/37 – Grundlagen der Programmierung 2 (Typen-A) – 20/37 – polymorphe Typen; Unifikation Unifikation: Regelbasierter Algorithmus Man braucht 4 Regeln, die auf (E; G) operieren: s :: σ → τ , . t :: ρ und γ ist allgemeinster Unifikator von σ = ρ (s t) :: γ(τ ) E: G: Menge von Typgleichungen, Lösung; mit Komponenten der Form x 7→ t. Beachte: (die Typvariablen in ρ müssen vorher umbenannt werden.) Grundlagen der Programmierung 2 (Typen-A) – 21/37 – Unifikation: Die Regeln x, y ist Typvariable si , ti , s, t sind Typen, f, g sind Typkonstruktoren Grundlagen der Programmierung 2 (Typen-A) – 22/37 – Unifikation: Regelbasierter Algorithmus Start mit G = ∅; E: zu lösende Gleichung(en) • • • • . G; {x = x} ∪ E G; E . G; {t = x} ∪ E Wenn t keine Variable ist . G; {x = t} ∪ E . G; {x = t} ∪ E Wenn x nicht in t vorkommt G[t/x] ∪ {x 7→ t}; E[t/x] . G; {(f s1 . . . sn ) = (f t1 . . . tn )} ∪ E . . G; {s1 = t1 , . . . , sn = tn )} ∪ E Ersetzung: Effekt: Fehlerabbruch, wenn: . • x = t in E, x 6= t und x kommt in t vor. (Occurs-Check) • . (f (. . .) = g(. . .) kommt in E vor und f 6= g. (Clash) Fehlerabbruch bedeutet: nicht typisierbar E[t/x]: alle Vorkommen von x werden durch t ersetzt G[t/x]: jedes y 7→ s wird durch y 7→ s[t/x] ersetzt Grundlagen der Programmierung 2 (Typen-A) – 23/37 – Grundlagen der Programmierung 2 (Typen-A) – 24/37 – Beispiel mit Typvariablen Noch ein Beispiel Berechne Typ von (map id) map:: id:: Gesuchter Typ: (a → b) a0 → a0 Berechne Typ von (map head) → ([a] → [b]) map:: head:: Gesuchter Typ: γ([a] → [b]) . Regelanwendung benötigt Lösung γ von (a → b) = (a0 → a0 ): G ∅; ∅; {a 7→ a0 }; {a 7→ a0 , b 7→ a0 }; {a 7→ a0 , b 7→ a0 }; – 25/37 – Grundlagen der Programmierung 2 (Typen-A) – 26/37 – Beispiel. Linksfaltung: (foldl (:) [])? foldr :: (a -> b -> b) -> b -> [a] -> b (:) :: a -> [a] -> [a] umbenannt: c -> [c] -> [c] umbenannt: [d] ([]) :: [a] Grundlagen der Programmierung 2 (Typen-A) E . {(a → b) = ([a0 ] → a0 )} . . {a = [a0 ], b = a0 } . 0 {b = a } ∅ Einsetzen der Lösung γ = {a 7→ [a0 ], b 7→ a0 } in [a] → [b] ergibt: 0 0 (map head) :: ([[a ]] → [a ]). Beispiel (foldr (:) []) :: ?? (foldr (:) [])::[c] → [c] γ([a] → [b]) G ∅; ∅; {a 7→ [a0 ]}; {a 7→ [a0 ], b 7→ a0 }; {a 7→ [a0 ], b 7→ a0 }; Einsetzen der Lösung γ = {a 7→ a0 , b 7→ a0 } in [a] → [b] ergibt: 0 0 (map id) :: ([a ] → [a ]). G ∅ {b 7→ [d]} {b 7→ [d]} {b 7→ [d], a 7→ c} {b 7→ [d], a 7→ c} {b 7→ [c], a 7→ c, d 7→ c} {b 7→ [c], a 7→ c, d 7→ c} {b 7→ [c], a 7→ c, d 7→ c} → ([a] → [b]) . Regelanwendung benötigt Lösung γ von (a → b) = ([a0 ] → a0 ): E . {(a → b) = (a0 → a0 )} . . {a = a0 , b = a0 } . 0 {b = a } ∅ Grundlagen der Programmierung 2 (Typen-A) (a → b) [a] → a foldl :: (a -> b -> a) -> a -> [b] -> a (:) :: a -> [a] -> [a] umbenannt: c -> [c] -> [c] umbenannt: [d] ([]) :: [a] E . . {a → b → b = c → [c] → [c], b = [d]} . {a → [d] → [d] = c → [c] → [c]} . . . {a = c, [d] = [c], [d] = [c]} . . {[d] = [c], [d] = [c]} . . {d = c, [d] = [c]} . {[c] = [c]} . {c = c} {} = γ([a] → b) G ∅ {a 7→ [d]} {a 7→ [d]} {a 7→ [d], b 7→ [c]} {a 7→ [d], b 7→ [c]} {a 7→ [d], b 7→ [[d]], c 7→ [d]} {a 7→ [d], b 7→ [[d]], c 7→ [d]} nicht lösbar, da d in [d] echt E . . {a → b → a = c → [c] → [c], a = [d]} . {[d] → b → [d] = c → [c] → [c]} . . . {[d] = c, b = [c], [d] = [c]} . . {[d] = c, [d] = [c]} . . {c = [d], [d] = [c]} . {[d] = [[d]]} . {d = [d]} vorkommt (foldl (:) []) ist nicht typisierbar! – 27/37 – Grundlagen der Programmierung 2 (Typen-A) – 28/37 – Beispiel zu Typberechnung Beispiele zu polymorpher Typberechnung Typ von map length map :: (a → b) → ([a] → [b]), length :: [a0 ] → Int (map length) :: ? = γ([a] → [b]) Berechne Typ der Liste [1]: 1 : Int . Unifiziere (a → b) = [a0 ] → Int G ∅; ∅; {a 7→ [a0 ]}; {a 7→ [a0 ], b 7→ Int}; (:) :: a → [a] → [a] 1 : [] ::? [] :: [b] . . Anwendungsregel ergibt Gleichungen: {a = Int, [a] = [b]} E . {(a → b) = ([a0 ] → Int)} . . {a = [a0 ], b = Int} . {b = Int} ∅ Lösung: γ = {a 7→ Int} Typ (1 : []) :: [Int] Somit: (map length) :: γ([a] → [b]) = [[a0 ]] → [Int] Grundlagen der Programmierung 2 (Typen-A) – 29/37 – Beispiel zu Typfehler [1, ’a’] hat keinen Typ: • 1 : ( ’a’ : []) • 1 :: Integer, [] :: [b], ’a’ :: Char • (1 :) :: [Integer] → [Integer] und (’a’:[]) :: [Char]. – 30/37 – Beispiel zu Typfehler im Interpreter Prelude> [1,’a’] ←- (Typen der Konstanten.) Kein Typ als Resultat, denn: . [Integer] = [Char] ist nicht lösbar. Grundlagen der Programmierung 2 (Typen-A) Grundlagen der Programmierung 2 (Typen-A) – 31/37 – <interactive>:1:1: No instance for (Num Char) arising from the literal ‘1’ at <interactive>:1:1 Possible fix: add an instance declaration for (Num Char) In the expression: 1 In the expression: [1, ’a’] In the definition of ‘it’: it = [1, ’a’] Grundlagen der Programmierung 2 (Typen-A) – 32/37 – Typ eines Ausdrucks Typisierung von Funktionen und Ausdrücken Kompexe Sprachkonstrukte: Typ von (map quadrat [1,2,3,4]) : • map:: (a → b) → [a] → [b] rekursiv definierte Funktionen Lambda-Ausdrücke, let-Ausdrücke Listen-Komprehensionen. Typcheckalgorithmus von Robin Milner (in Haskell und ML) • ist schnell, (i.a.) • liefert allgemeinste (Milner-)Typen • benutzt Unifikation (eine optimierte Variante) • quadrat:: Integer → Integer, und [1, 2, 3, 4] :: [Integer]. • γ= {a 7→ Integer, b 7→ Integer}. • Das ergibt: γ(a) = Integer, γ([a]) = [Integer], γ([b]) = [Integer]. • • Resultat: γ([b]) = [Integer] • Grundlagen der Programmierung 2 (Typen-A) – 33/37 – Typisierung von Funktionen, Milner Grundlagen der Programmierung 2 (Typen-A) – 34/37 – Typisierung und Reduktion Beachte: Satz zur Milner-Typisierung in Haskell: Sei t ein getypter Haskell-Ausdruck, ohne freie Variablen. Dann wird die Auswertung des Ausdrucks t nicht mit einem Typfehler abbrechen. Nach Reduktionen kann ein Ausdruck mehr Typen (bzw. einen allgemeineren Typ) haben als vor der Reduktion Beispiel: if 1 > 0 then [] else [1] Allgemeine Aussage zu starken Typsystemen Es gibt keine dynamischen Typfehler, wenn das Programm statisch korrekt getypt ist Grundlagen der Programmierung 2 (Typen-A) schlechte worst-case Komplexität: in seltenen Fällen exponentieller Zeitbedarf Liefert in seltenen Fällen nicht die allgemeinsten möglichen polymorphen Typen – 35/37 – :: [Integer] arithmetische-Reduktion: −→ if True then [] else [1] :: [Integer] Case-Reduktion: −→ [] :: [a] Grundlagen der Programmierung 2 (Typen-A) – 36/37 – Typisierung und Reduktion weiteres Beispiel: (if 1 > 0 then foldr else foldl) :: (a -> a -> a) -> a -> [a] -> a Reduktion ergibt als Resultat: foldr:: (a -> b -> b) -> b -> [a] -> b der Typ ist allgemeiner geworden! Grundlagen der Programmierung 2 (Typen-A) – 37/37 –