Haskell, Typen, und Typberechnung Grundlagen der Programmierung 3 A Typen, Typberechnung und Typcheck Prof. Dr Manfred Schmidt-Schauß Ziele: Haskells Typisierung Typisierungs-Regeln Typ-Berechnung Sommersemester 2014 Milners Typcheck Grundlagen der Programmierung 2 (Typen-A) 2/35 Typisierung in Haskell Überladung und Konversion in Haskell Haskell hat eine starke, statische Typisierung. mit parametrisch polymorphen Typen. jeder Ausdruck muss einen Typ haben 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. Haskell: keine Typkonversion Es gibt Überladung: z.b. arithmetische Operatoren: +,., / ganze Zahlkonstanten sind überladen Grundlagen der Programmierung 2 (Typen-A) 3/35 Grundlagen der Programmierung 2 (Typen-A) 4/35
Typisierung, Begriffe Syntax von Typen in Haskell (ohne Typklassen) polymorph: parametrisch polymorph: Beispiel Schema: Ein Ausdruck kann viele Typen haben (vielgestaltig, polymorph). Die (i.a. unendliche) Menge der Typen entspricht einem schematischen Typausdruck [a]->int Typ ::= Basistyp ( Typ ) Typvariable Typkonstruktor n { Typ } n (n = Stelligkeit) Basistyp ::= Int Integer Float Rational Char Instanzen: [Int]->Int, [Char]->Int, [[Char]]->Int Grundlagen der Programmierung 2 (Typen-A) 5/35 Grundlagen der Programmierung 2 (Typen-A) 6/35 Syntax von Typen Interpretation der Typen Typkonstruktoren können benutzerdefiniert sein (z.b. Baum a) Vordefinierte Liste [ ] Typkonstruktoren: Tupel (.,...,.) Funktionen (Stelligkeit 2, Infix) Konvention zu Funktionstypen mit a b c d bedeutet: a (b (c d)). length :: [a] -> Interpretation: Int Für alle Typen typ ist length eine Funktion, die vom Typ [typ] Int ist. Grundlagen der Programmierung 2 (Typen-A) 7/35 Grundlagen der Programmierung 2 (Typen-A) 8/35
Komposition Typ der Komposition Beispiel: Komposition von Funktionen: komp::(a -> b) -> (c -> a) -> c -> b komp f g x = f (g x) 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(x 2 ) und quadrat. sin entspricht (sin(x)) 2. Erklärung zum Typ von komp, wobei {a,b,c} Typvariablen sind. Ausdruck: f komp g bzw. f. g (a->b) -> (c->a) -> c->b Typ von (.) (τ 1 -> τ 2 ) Typ von f (τ 3 -> τ 1 ) Typ von g τ 3 Typ des Arguments x der Komposition f. g τ 2 Typ des Resultats der Komposition f(g x) (τ 3 -> τ 2 ) Typ von (f. g) x :: τ 3 g τ 1 f τ 2 Grundlagen der Programmierung 2 (Typen-A) 9/35 Grundlagen der Programmierung 2 (Typen-A) 10/35 Typregeln Typregel Anwendung : mehrere Argumente Wie berechnet man Typen von Ausdrücken? Anwendung von Funktionsausdruck auf Argument s :: σ τ, t :: σ (s t) ::? Beispiele: quadrat :: Int Int, 2 :: Int (quadrat 2) ::? + :: Int (Int Int), 1 :: Int (1 +) ::?, 2:: Int ((1 +) 2) ::? Beispiele s :: σ 1 σ 2... σ n τ, t 1 :: σ 1,..., t n :: σ n (s t 1... t n ) :: τ + :: Int Int Int, 1 :: Int, 2 :: Int (+ 1 2) :: Int + :: Int Int Int (1 + 2) :: Int Grundlagen der Programmierung 2 (Typen-A) 11/35 Grundlagen der Programmierung 2 (Typen-A) 12/35
Beispiel Anwendungsregel für polymorphe Typen even::int -> Bool (.)::(a -> b) -> (c -> a) -> c -> b quadrat::int -> Int a -> b. = Int -> Bool c -> b. = Int -> 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 τ even. quadrat :: Int -> Bool Beispiel Typsubstitution: Instanz: γ = {a Char, b Float} γ([a] Int) = [Char] Int Grundlagen der Programmierung 2 (Typen-A) 13/35 Grundlagen der Programmierung 2 (Typen-A) 14/35 Anwendungsregel für polymorphe Typen Beispiel zur polymorphen Anwendungsregel Typ von (map quadrat)? s :: σ τ, t :: ρ und γ(σ) = γ(ρ) (s t) :: γ(τ) Berechnet den Typ von (s t) wenn Typen von s, t schon bekannt sind Hierbei ist zu beachten: die Typvariablen in ρ müssen vorher umbenannt werden, so dass σ und ρ keine gemeinsamen Typvariablen haben. map :: (a b) ([a] [b]) Instanziiere mit: γ = {a Int, b Int} ergibt: map :: (Int Int) ([Int] [Int]) Regelanwendung ergibt: map :: (Int Int) ([Int] [Int]), quadrat :: (Int Int) (map quadrat) :: [Int] [Int] Grundlagen der Programmierung 2 (Typen-A) 15/35 Grundlagen der Programmierung 2 (Typen-A) 16/35
Polymorphe Regel für n Argumente Wie berechnet man die Typsubstitutionen? s :: σ 1 σ 2... σ n τ, t 1 :: ρ 1,..., t n :: ρ n und i : γ(σ i ) = γ(ρ i ) (s t 1... t n ) :: γ(τ) Unifikation: Berechnung der allgemeinsten Typsubstitution im Typberechnungsprogramm bzw Typchecker Die Typvariablen in ρ 1,..., ρ n müssen vorher umbenannt werden. Unifikation wird benutzt im Typchecker von Haskell! Beachte: verwende möglichst allgemeines γ (kann berechnet werden; s.u.) Grundlagen der Programmierung 2 (Typen-A) 17/35 Grundlagen der Programmierung 2 (Typen-A) 18/35 polymorphe Typen; Unifikation Unifikation: Regelbasierter Algorithmus Man braucht 4 Regeln, die auf (E; G) operieren: s :: σ τ, t :: ρ und γ ist allgemeinster Unifikator von σ. = ρ (s t) :: γ(τ) Hierbei ist zu beachten: die Typvariablen in ρ müssen vorher umbenannt werden. E: Menge von Typgleichungen, G: Lösung; mit Komponenten der Form x t. Beachte; x sind Typvariablen, t sind Typen, f, g sind Typkonstruktoren Grundlagen der Programmierung 2 (Typen-A) 19/35 Grundlagen der Programmierung 2 (Typen-A) 20/35
Unifikation: Die Regeln Unifikation: Regelbasierter Algorithmus Start mit G = ; E G; {x. = x} E G; E G; {t. = x} E G; {x. = t} E G; {x =. t} E G[t/x] {x t}; E[t/x] Wenn t keine Variable ist G; {(f s 1... s n ). = (f t 1... t n )} E G; {s 1. = t1,..., s n. = tn )} E Wenn x nicht in t Fehlerabbruch, wenn: x =. t in E, x t und x kommt in t vor. (f(...) =. g(...) kommt in E vor und f g. Fehlerabbruch bedeutet: nicht typisierbar Ersetzung: Effekt: E[t/x]: alle Vorkommen von x werden durch t ersetzt G[t/x]: jedes y s wird durch y s[t/x] ersetzt Grundlagen der Programmierung 2 (Typen-A) 21/35 Grundlagen der Programmierung 2 (Typen-A) 22/35 Beispiel mit Typvariablen Beispiel mit Typvariablen Berechne Typ von (map id) map:: (a b) [a] [b] id:: a a Regelanwendung benötigt Lösung von (a b). = (a a ): G E ; {(a b) =. (a a )} ; {a =. a, b =. a } {a a }; {b =. a } {a a, b a }; {a a, b a }; map :: (a b) ([a] [b]), id :: a a (map id) :: γ([a] [b]) Einsetzung der Lösung γ = {a a, b a } ergibt (map id) :: ([a ] [a ]). Grundlagen der Programmierung 2 (Typen-A) 23/35 Grundlagen der Programmierung 2 (Typen-A) 24/35
Beispiel (foldr (:) []) ::?? foldr :: (a -> b -> b) -> b -> [a] -> b (:) :: a -> [a] -> [a] ([]) :: [a] G E {a b b =. c [c] [c], b =. [d]} {b [d]} {a [d] [d] =. c [c] [c]} {b [d]} {a =. c, [d] =. [c], [d] =. [c]} {b [d], a c} {[d] =. [c], [d] =. [c]} {b [d], a c} {d =. c, [d] =. [c]} {b [c], a c, d c} {[c] =. [c]} {b [c], a c, d c} {c =. c} {b [c], a c, d c} {} (foldr (:) [])::[c] [c] = γ([a] b) Beispiel. Linksfaltung: (foldl (:) [])? foldl :: (a -> b -> a) -> a -> [b] -> a (:) :: a -> [a] -> [a] ([]) :: [a] G E {a b a) =. c [c] [c], a =. [d]} {a [d]} {[d] b [d] =. c [c] [c]} {a [d]} {[d] =. c, b =. [c], [d] =. [c]} {a [d], b [c]} {[d] =. c, [d] =. [c]} {a [d], b [c]} {c =. [d], [d] =. [c]} {a [d], b [[d]], c [d]} {[d] =. [[d]]} {a [d], b [[d]], c [d]} {d =. [d]} nicht lösbar Grundlagen der Programmierung 2 (Typen-A) 25/35 Grundlagen der Programmierung 2 (Typen-A) 26/35 Beispiel zu Typberechnung Beispiele zu polymorpher Typberechnung Typ von map length map :: (a b) ([a] [b]), length :: [a ] Int (map length) ::? Unifiziere (a b). = [a ] Int G E ; {(a b) =. ([a ] Int)} ; {a =. [a ], b =. Int} {a [a ]}; {b =. Int} {a [a ], b Int}; Berechne Typ der Liste [1]: 1 : Int (:) :: a [a] [a] [] :: [b] 1 : [] ::? Anwendungsregel ergibt Gleichungen: {a. = Int, [a]. = [b]} Lösung: γ = {a Int} Typ (1 : []) :: [Int] Somit: (map length) :: γ([a] [b]) = [[a ]] [Int] Grundlagen der Programmierung 2 (Typen-A) 27/35 Grundlagen der Programmierung 2 (Typen-A) 28/35
Beispiel zu Typfehler Beispiel zu Typfehler im Interpreter [1, a ] hat keinen Typ: 1 : ( a : []) 1 :: Integer, [] :: [b], a :: Char (Typen der Konstanten.) (1 :) :: [Integer] [Integer] und ( a :[]) :: [Char]. Kein Typ als Resultat, denn: [Integer]. = [Char] ist nicht lösbar. Prelude> [1, a ] <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) 29/35 Grundlagen der Programmierung 2 (Typen-A) 30/35 Typ eines Ausdrucks Typisierung von Funktionen und Ausdrücken Typ von (map quadrat [1,2,3,4]) : map:: (a b) [a] [b] quadrat:: Integer Integer, und [1, 2, 3, 4] :: [Integer]. γ = {a Integer, b Integer}. Das ergibt: γ(a) = Integer, γ([a]) = [Integer], γ([b]) = [Integer]. Resultat: γ([b]) = [Integer] Problematik: rekursiv definierte Funktionen Lambda-Ausdrücke, let-ausdrücke Listen-Komprehensionen. Typcheckalgorithmus von Robin Milner (in Haskell und ML) ist schnell, liefert allgemeinste (Milner-)Typen benutzt Unifikation (eine optimierte Variante) schlechte worst-case Komplexität: in seltenen Fällen exponentieller Zeitbedarf Liefert in seltenen Fällen nicht die allgemeinsten möglichen polymorphen Typen Grundlagen der Programmierung 2 (Typen-A) 31/35 Grundlagen der Programmierung 2 (Typen-A) 32/35
Typisierung von Funktionen, Milner Typisierung und Reduktion 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. Beachte: Nach Reduktionen kann ein Ausdruck mehr Typen (bzw. einen allgemeineren Typ) haben als vor der Reduktion Beispiel: if 1 > 0 then [] else [1] :: [Integer] Allgemeine Aussage zu starken Typsystemen Es gibt keine dynamischen Typfehler, wenn das Programm statisch korrekt getypt ist arithmetische-reduktion: if True then [] else [1] Case-Reduktion: [] :: [Integer] :: [a] Grundlagen der Programmierung 2 (Typen-A) 33/35 Grundlagen der Programmierung 2 (Typen-A) 34/35 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) 35/35