Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Programmieren in Haskell Das Haskell Typsystem Peter Steffen Robert Giegerich Universität Bielefeld Technische Fakultät 22.01.2010 1 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Belauscht ... Lisa Lista: “Ohne Typen keine korrekten Programme!” Harry Hacker: “Ach was! Korrekte Programme brauchen keine Typen!” Wer hat recht? 2 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Einfache Datentypen (vordefiniert) Die einfachsten Datentypen: Bool (), genannt Void Char Int, Integer Rational, Float, Double Int -> Int, Int -> Musik -> Musik, etc. In einer funktionalen Sprache sind auch Funktionen Daten “erster Klasse” – sie können nicht nur auf Argumente angewandt werden, sondern auch in Datenstrukturen verpackt, als Argumente übergeben, oder zu neuen Funktionen zusamengesetzt werden, also Ergebnisse sein. Nur Vergleichen (EQ) und Ausdrucken (Show) kann man sie nicht. 3 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Algebraische Datentypen Algebraische Datentypen werden durch Konstruktoren gebildet. Eigentlich stellen sie die Formeln dar, mit denen dann gerechnet wird. Musik (mit Konstruktoren Instr, Tempo, Note, Pause, *, +) Bool (mit Konstruktoren True, False) Ordering (mit Konstruktoren LT, EQ, GT) Von diesen Beispielen ist nur Musik ein rekursiver Datentyp, der beliebig viele (und aus viele Teilen zusammengesetzte) Werte enthält. 4 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Monomorphe Listen Listen mit Elementen vom Typ Int: data Intlist = IntNil | IntCons Int IntList Einwände: Das ist wenig nützlich – für jeden weiteren Element-Datentyp muss man einen neuen Listentyp einführen. (In manchen Programmiersprachen ist das tatsächlich so!) Viele Operationen mit Listen hängen nicht vom Elementtyp ab – die möchte man nur einmal programmieren! 5 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Parametrischer Typ-Polymorphismus Das Haskell-Typkonzept ist so stark, dass Typ-korrrekte Proramme oft auch schon korrekt sind. Das liegt am parametrischen Typ-Polymorphismus. Typen werden dadurch flexibler, aber weiter gilt der alte Grundsatz: Ein typ-korrektes Programm erzeugt zur Laufzeit keine Typfehler. oder auch Well-typed programs don’t go wrong. 6 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Parametrischer Typ-Polymorphismus Haskell erlaubt parametrisierte Typen: data [a] = [] | a:[a] -- polymorphe Listen -- beachte: alle Elemente haben den gleichen Typ data Tree a = Leaf a | Br (Tree a) (Tree a) data TTree a b = Leaf a | Br (Tree b a) [a] (Tree b a) -- hier wechseln die Typen pro Ebene, -- aber streng kontrolliert f g 7 :: [a] -> [a] :: Tree a -> [a] Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Typdeklarationen Typdeklarationen in Haskell sind optional. Sie dienen der Dokumentation der bewußten Einschränkung des Typs (enger als nötig), z.B. reversi:: [Int] -> [Int] reversi xs = reverse xs wenn man möchte, dass reversi NUR auf Int-Listen angewandt werden kann. Auch ohne Typdeklarationen können alle Typen bestimmt und geprüft werden. 8 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Typinferenz Grundlegendes zur Typinferenz: Jeder Ausdruck in einem Haskell-Programm hat einen allgemeinsten Typ. Dieser Typ kann automatisch bestimmt werden durch Typinferenz. Beim Rechnen spielen die Typen keine Rolle (mehr). Meistens stimmt der inferierte Typ mit dem deklarierten (sofern vorhanden) überein. Andernfalls gilt: Der deklarierte Typ ist allgemeiner: FEHLER Der deklarierte Typ ist spezieller: Der allgemeine Typ wird entsprechend eingeschränkt. DieTypen sind unvergleichbar: FEHLER 9 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Allgemeiner und spezieller ... a -- der allgemeinste Typ, den es gibt -- (a ist beliebige Typvariable) a -> b -- der allgemeinste Funktionstyp, den es gibt Ein Typ ist spezieller als ein anderer, wenn er aus ihm durch (konsistentes) Einsetzen für Typvariablen entsteht. Hier eine Kette von Spezialisierungen: a ==> b ==> (a -> b) => a -> (c -> d) ==> a -> b -> b ==> a -> [a] -> [a] -- das ist der Typ von (:) ==> Tree b -> [Tree b] -> [Tree b] -- (:) in einer Anwendung 10 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Beispiele zur Typinferenz Der Typ einer polymorphen Funktion spezialsiert sich in der Anwendung: (:) (’a’:"braham") (: "xx") (’x’ :) ("x":) :: :: :: :: :: a -> [a] -> [a] [Char] Char -> [Char] [Char] -> [Char] [[Char]] -> [[Char]] Hugs> :type (: ’a’) ERROR - Type error in application *** Expression : (: ’a’) *** Term : (:) *** Type : b -> [b] -> [b] *** Does not match : a -> Char -> [b] Scheitert die Typinferenz, liegt ein Fehler vor. 11 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Typinferenz für map Wir gehen von der Definition aus: map f [] = [] map f (x:xs) = f x : map f xs ermittelter Typ c -> d -> e c -> d -> [b] c -> [a] -> [b] (f -> g) -> [a] -> [b] (a -> g) -> [a] -> [b] (a -> b) -> [a] -> [b] ------- Begründung map hat 2 Argumente das Ergebnis ist eine Liste das 2. Argument ist eine Liste das 1.Argument ist eine Funktion, die Argumente vom Typ a erhält, und Ergebnisse vom Typ b liefert. Den allgemeinsten Typ erhalten wir, weil wir nirgendwo eine unnötige Annahme machen. 12 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Typinferenz eines Audrucks Wir gehen von bereits bekannten Typen für Zahlen und vordefinierte Funktionen aus und leiten eine Typaussage für einen Ausdruck ab: map (1:)::h map:: (a -> b) -> [a] -> [b] (:):: c -> [c] -> [c] 1::Int c == Int (:):: Int -> [Int] -> [Int] (1:):: [Int] -> [Int] 13 --------- der Ausdruck hat einen Typ bereits bekannt bereits bekannt bereits bekannt 1 erstes Argument von (:) Typ von (:) in diesem Kontext (:) angewandt aufs erste Argument bleibt eine Funktion des zweiten Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen [a] -> [b] == [Int] -> [Int] -- (1:) ist erstes Argument von map a == Int, b == Int -- Konsequenz des vorigen map:: ([Int] -> [Int]) -> [[Int]] -> [[Int]] -- spezieller Typ von -- map in diesem Ausdruck map (1:):: [[Int]] -> ][Int]]-- map angewandt auf erstes Argument -- bleibt eine Funktion des zweiten h == [[Int]] -> [[Int]] -- allgemeinster Typ für map (1:) ... und das stimmt überein mit dem, was diese Funktion berechnet: Hugs> map (1:) [[5..9],[20..24],[2,4..8]] [[1,5,6,7,8,9],[1,20,21,22,23,24],[1,2,4,6,8]] 14 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Typinferenz mit Typklassen In Wirklichkeit ist es noch etwas komplizierter. Man weiss ja nur, dass 1 eine Zahl ist, also der Typklasse NUM angehört. Hugs> :type 1 1 :: Num a => a Hugs> :type (1:) (1 :) :: Num a => [a] -> [a] Hugs> :type map (1:) map (1 :) :: Num a => [[a]] -> [[a]] Hugs> :type map ((1::Integer):) map (1:) :: [[Integer]] -> [[Integer]] Hugs> :type map (1.5:) map (1.5 :) :: Fractional a => [[a]] -> [[a]] 15 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Polymorphe Funktionen Polymorphe Funktionen können auf alle Daten angewandt werden, zu deren Typ sich ihr allgemeiner Typ spezialisieren lässt. Vorteil: Kompakter Code, wenig Fehler. 16 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Was sagt ihr Typ über die Funktion? Wir kennen: id :: a -> a ix x = x Welche andere Funktionen gibt es noch von diesem recht allgemeinen Typ, (abgesehen von umständlicheren Definitionen der gleichen Funktion)? Keine. 17 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Was sagt ihr Typ über die Funktion? Wir kennen: id :: a -> a ix x = x Welche andere Funktionen gibt es noch von diesem recht allgemeinen Typ, (abgesehen von umständlicheren Definitionen der gleichen Funktion)? Keine. 17 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Was sagt ihr Typ über die Funktion? (2) Welche Funktion hat den noch allgemeineren Typ f :: a -> b 18 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Was sagt ihr Typ über die Funktion? (2) Welche Funktion hat den noch allgemeineren Typ f :: a -> b Die überall undefinierte Funktion ... f :: a -> b f x = f x ... und sonst keine. 19 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Fluch der Allgemeinheit Auf der ersten Blick scheint es paradox: Die Funktionen mit den allgemeinsten Typen f:: a -> b f:: a -> a sind die “nutzlosesten” Funktionen! 20 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Polymorphe Funktionen höherer Ordnung (1) Was mag das sein: sRoL:: solution -> (a -> [a] -> solution -> solution)-> [a] -> solution sRoL base extend = rec where rec [] = base rec (x:xs) = extend x xs (rec xs) 21 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Polymorphe Funktionen höherer Ordnung (1) Was mag das sein: sRoL:: solution -> (a -> [a] -> solution -> solution)-> [a] -> solution sRoL base extend = rec where rec [] = base rec (x:xs) = extend x xs (rec xs) 21 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen sRoL in Worten sRoL ist eine Funktion, die als Arfumente einen Wert und eine Funktion erwartet. Angewandt auf diese, ergibt sie eine Funktion, die Listen verarbeitet und dabei für die leere Liste den Wert einsetzt, und ansonsten den Listenrest verarbeitet und sodann das Ergebnis abhängig vom Listenkopf modifiziert. Oder anders gesagt: sRoL ist das SCHEMA der strukturellen Rekursion auf Listen. 22 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen sRoL in Worten sRoL ist eine Funktion, die als Arfumente einen Wert und eine Funktion erwartet. Angewandt auf diese, ergibt sie eine Funktion, die Listen verarbeitet und dabei für die leere Liste den Wert einsetzt, und ansonsten den Listenrest verarbeitet und sodann das Ergebnis abhängig vom Listenkopf modifiziert. Oder anders gesagt: sRoL ist das SCHEMA der strukturellen Rekursion auf Listen. 22 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen sRoL angewandt isort = sRoL [] (\a _ s -> insert a s) insert a = sRoL [a] (\b x s -> if a <= b then a:b:x else b:s) Man muss sich klarmachen, dass s jeweils die (rekursiv errechnete) Lösung der Aufgabe für x darstellt! 23 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen sRoL Varianten sRoL bezieht sich explizit auf den Datentyp Liste. Es werden ja die beiden Konstruktoren [] und (:) explizit benutzt. Für andere Datentypen wie Musik oder Tree muss ein eigenes Schema definiert werden. Das wird noch eleganter bei der Wohlfundierten Rekursion! 24 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Schema der Wohlfundierten Rekursion problem steht für den Datentyp, für den wir ein Problem mit Wohlfundierter Rekursion lösen wollen. daC:: (problem -> Bool) -> (problem -> solution) -> (problem -> [problem]) -> ([solution] -> solution) -> problem -> solution ----- easy solve divide conquer daC easy solve divide conquer = rec where rec x = if easy x then solve x else conquer [rec y| y <- divide x] 25 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Anwendung von daC Implementierung des mergesort: msort = daC (\x -> drop 1 x == []) (\x -> x) (\x -> let k = length x ‘div‘ 2 in [take k x, drop k x] (\[s,t] -> merge s t) Wo ist die Rekursion geblieben? Sie wird vom Scheme daC eingebracht – das, worauf merge angewandt wird, ist der msort von (take k x) und (drop k x). 26 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Anwendung von daC Implementierung des mergesort: msort = daC (\x -> drop 1 x == []) (\x -> x) (\x -> let k = length x ‘div‘ 2 in [take k x, drop k x] (\[s,t] -> merge s t) Wo ist die Rekursion geblieben? Sie wird vom Scheme daC eingebracht – das, worauf merge angewandt wird, ist der msort von (take k x) und (drop k x). 26 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Schlussfolgerungen Funktionen höherer Ordnung sind ein machtvolles Hilfsmittel: Sie erlauben, beliebige Schemata der Benutzung von funktionellen Bauteilen zu definieren Sie ersetzen das, was man in anderen Sprachen Kontrollstruktur nennt. Sie erlauben dem Programmierer, seine eigenen Kontrollstrukturen zu definieren. Dadurch lassen sich anwendungsbezogene Programmierstile entwickeln – Beispiele sind Pretty printing Combinator Parsing Algebraic Dynamic Programming 27 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Typklassen Eine Typklasse ist eine Menge von Typen. Sie ist charakterisiert durch eine Menge von Funktionen, die auf allen Typen dieser Klasse und mit dem gleichen Namen definiert sind. Diese Funktionen nennt man in diesem Zusammenhang gerne “Methoden”. Vordefinierte Typklassen sind Show – mit Methoden show, read, ... Eq – mit Methoden ==, / = Ord – mit Methoden <, >, >, 6 ... Num – mit Methoden +, −, ∗, ... und viele andere. 28 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Neue Typklassen Neue Typklassen werden durch Klassendefinitionen eingeführt, die die Typen der Operationen (hier gerne “Methoden” genannt) deklarieren. Es können auch einige der Methoden als Default definiert werden, indem sie sich gegenseitig benutzen. Wenn ein Typ zur Instanz einer Klasse erklärt wird, müssen die Methoden implementiert werden. 29 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Beispiel: Klasse Sequenzen class Sequence s empty :: single :: isEmpty :: isSingle :: vorndran :: hintan :: hd :: tl :: app :: len :: where s a a -> s a s a -> Bool s a -> Bool a -> sa -> s a sa -> a -> s a s a -> a s a -> s a s a -> s a -> s a s a -> Int single a = vorndran a empty isSingle s = not(isEmpty s) && isEmpty (tl s) len s = if empty s then 0 else 1 + len (tl s) 30 Programmieren in Haskell Einfache Datentypen Parametrischer Typ-Polymorphismus Polymorphe Funktionen Typklassen Instanziierung Ein Datentyp kann zur Instanz einer Klasse erklärt werden: instance Sequence [] where ... instance Tree [] where ... Hier müssen jeweils die Methoden implementiert werden. Fehlt eine Implementierung, werden die Defaults aus der Klassendefinition eingesetzt. Diverse Instanziierungen von Sequence findet man im Skript. 31 Programmieren in Haskell