Programmieren in Haskell Stefan Janssen Programmieren in Haskell Typ-Polymorphismus Stefan Janssen Universität Bielefeld AG Praktische Informatik November 12, 2014 Das Haskell Typ-System Programmieren in Haskell Wir beginnen mit einer Wiederholung des Bekannten: In allen Programmiersprachen sind Typ-Konzepte ein prägendes Element. Das Haskell Typ-System hat zwei grundlegende Konzepte: Parametrischer Typ-Polymorphismus Typ-Klassen Andere Konzepte: untypisierte Sprachen monomorphe Typen Overloading Objektklassen und Vererbung Stefan Janssen Strong typing Programmieren in Haskell Stefan Janssen Unter “strong typing” (strenge Typisierung) in einer Programmiersprache versteht man: Alle Typen werden durch den Compiler überprüft Zur Laufzeit der übersetzten Programme können keine Typfehler auftreten Typ-Sicherheit ist erreichbar ohne Effizienzverlust zur Laufzeit Praktische Bedeutung des Typ-Systems Programmieren in Haskell Stefan Janssen Fehler im Programmentwurf kann man grob unterteilen: “Denkfehler”: Der Algorithmus löst das vorgegebene Problem nicht “Codierfehler”: Die algorithmische Idee ist richtig, aber ihre Formulierung im Programm ist fehlerhaft Fehler der zweiten Art äußern sich häufig als Typfehler. Erkennt sie der Compiler, erspart dies viel Mühe beim Testen. Praktische Bedeutung des Typ-Systems Programmieren in Haskell Stefan Janssen Fehler im Programmentwurf kann man grob unterteilen: “Denkfehler”: Der Algorithmus löst das vorgegebene Problem nicht “Codierfehler”: Die algorithmische Idee ist richtig, aber ihre Formulierung im Programm ist fehlerhaft Fehler der zweiten Art äußern sich häufig als Typfehler. Erkennt sie der Compiler, erspart dies viel Mühe beim Testen. Der Programmier-Lehrling schlägt sich mit den Typ-Fehlern herum, der Könner genießt still. Warum Typ-Polymorphismus? 1 2 Ziel: Typsicherheit + Wiederverwendbarkeit von Code > length [] = 0 > length ( x : xs ) = 1 + length xs Was ist der Typ? length kann Listen jeden Typs verarbeiten, weil die Länge der Liste vom Element-Typ unabhängig ist. Programmieren in Haskell Stefan Janssen Warum Typ-Polymorphismus? 1 2 1 2 3 4 5 Ziel: Typsicherheit + Wiederverwendbarkeit von Code > length [] = 0 > length ( x : xs ) = 1 + length xs Was ist der Typ? length kann Listen jeden Typs verarbeiten, weil die Länge der Liste vom Element-Typ unabhängig ist. Programmieren in Haskell Stefan Janssen length :: String -> Int length :: [ Int ] -> Int length :: [ Bool ] -> Int length :: [ String ] -> Int length :: [[ String ]] -> Int -- und so weiter ...? Es soll nur EINE Definition und EINEN allgemeinen Typ von length geben: length::[a] -> Int. Monomorphe Typen Programmieren in Haskell 1 2 Monomorpher Typ: vollständig festgelegt, z.B. bei Konstanten True , Note ce (1/2) , " abraham " , Just " Justine " Bool , Musik , [ Char ] Maybe String Stefan Janssen Monomorphe Typen Programmieren in Haskell 1 2 1 2 3 Monomorpher Typ: vollständig festgelegt, z.B. bei Konstanten True , Note ce (1/2) , " abraham " , Just " Justine " Bool , Musik , [ Char ] Maybe String Monomorpher Typ: vollständig festgelegt, z.B. durch Deklaration x :: Integer , y :: Bool , 42:: Integer polynome :: [ Int ] -> Int length ’ :: [ Char ] -> Int Stefan Janssen Interessante Konstanten Programmieren in Haskell 1 Im Allgemeinen sind Konstanten monomorph, mit interessanten Ausnahmen: 42:: Int , 42:: Integer , 42:: Float , 42:: Double Zahlen gehören mehreren Typen an ⇒ wird unter “Typklassen” erklärt Stefan Janssen Interessante Konstanten Programmieren in Haskell 1 Im Allgemeinen sind Konstanten monomorph, mit interessanten Ausnahmen: 42:: Int , 42:: Integer , 42:: Float , 42:: Double Zahlen gehören mehreren Typen an ⇒ wird unter “Typklassen” erklärt 1 Was ist der Typ von Nothing in den folgenden Zeilen? if Just " Justine " /= Nothing then ... let x = Nothing in ... 2 Stefan Janssen Typ-Parameter Programmieren in Haskell 1 2 Polymorpher Typ: Typ mit Typ-Parametern x :: Tree a , y :: Maybe a , z ::[ a ] length ::[ a ] -> Int Stefan Janssen Typ-Parameter Programmieren in Haskell 1 2 1 2 3 4 5 Polymorpher Typ: Typ mit Typ-Parametern x :: Tree a , y :: Maybe a , z ::[ a ] length ::[ a ] -> Int Eigentlich ist fast alles polymorph: (++):: [ a ] -> [ a ] -> [ a ] concat :: [[ a ]] -> [ a ] map :: ( a -> b ) -> [ a ] -> [ b ] foldr :: ( a -> b -> b ) -> b -> [ a ] -> b foldr ’:: ( a -> a -> a ) -> a -> [ a ] -> a Der letzte Typ wäre für foldr zu speziell! Stefan Janssen Typ-Konstruktoren Programmieren in Haskell Stefan Janssen 1 2 3 4 5 Typen werden durch Typ-Ausdrücke beschrieben, bestehend aus Typ-Konstruktoren + Typ-Parametern [a] -- Listen a -> b -- Funktionen (a , b ) -- Paare , Tupel Maybe a -- Maybe Tree a -- Baeume Wie im Fall von Tree a können wir beliebige Datentypen definieren und damit selbst neue Typ-Konstruktoren einführen. Hierarchie polymorpher Tpen Programmieren in Haskell 1 Typen werden spezieller, wenn man für die Typ-Variablen Typ-Ausdrücke einsetzt. Der allgemeinste Typ ist einfach a Stefan Janssen Hierarchie polymorpher Tpen Programmieren in Haskell 1 1 1 Typen werden spezieller, wenn man für die Typ-Variablen Typ-Ausdrücke einsetzt. Der allgemeinste Typ ist einfach a Wir könennen ihn spezialisieren zu Bool String Integer Double oder auch wieder polymorph zu [a] a -> b Maybe a Tree a und wieder weiter zu ... (Tafel) Gibt es zu jedem Typ auch eine Funktion, die diesen Typ hat? Stefan Janssen Typ-Spezialisierung Programmieren in Haskell 1 2 In der Anwendung einer polymorphen Funktion spezialisiert sich ihr Typ: length " abraham " length :: String -> Int 3 4 5 length [ " abraham " , " bebraham " , " cebraham " ] length :: [ String ] -> Int 6 7 8 ’a ’: " braham " (:) :: Char -> [ Char ] -> Char Mit jedem Argument wird der Typ spezieller ... Stefan Janssen Typ-Inferenz Programmieren in Haskell Der allgemeinste Typ jeden Ausdrucks, jeder Anwendung, jeder Variable läßt sich automatisch bestimmen. Konsequenz: Typ-Deklarationen sind nicht notwendig, sondern optional Der allgemeinste Typ wird immer berechnet Allgemeinster Typ > deklarierter Typ: Vom Programmierer gewollte Typ-Einschränkung Allgemeinster Typ < deklarierter Typ: Fehler!! Obwohl optional, gibt man Typ-Deklarationen meistens an (Kontrolle, Dokumentation) Stefan Janssen Typ-Inferenz am Beispiel 1 2 Aus den definierenden Gleichungen einer Funktion für diese Funktion: map f [] = [] map f ( x : xs ) = f x : map f xs Dabei ist der Typ von (:) bekannt. Programmieren in Haskell Stefan Janssen Typ-Inferenz am Beispiel 1 2 1 Aus den definierenden Gleichungen einer Funktion für diese Funktion: map f [] = [] map f ( x : xs ) = f x : map f xs Dabei ist der Typ von (:) bekannt. Aus bekannten Typen der Variablen der Typ eines Ausdrucks: let f = map ( ’u ’:) in ... Hier sind die Typen von (:), (’u’:) und map (’u’:) zu bestimmen, während der allgemeinste Typ von (:) und map gegeben ist. Programmieren in Haskell Stefan Janssen Typ-Polymorphismus mit Typ-Klassen Programmieren in Haskell Stefan Janssen Aus unseren Beispielen kennen wir schon die Typ-Kontexte, die Typ-Parameter einschränken: Allgemeine Form von Typ-Ausdrücken 1 2 expr :: type expr :: typecontext = > type Type signatures Programmieren in Haskell Allgemeine Form von Typ-Deklarationen 1 2 vars :: type vars :: typecontext = > type mit vars = var1 , . . . , varn , n > 0 Stefan Janssen Type signatures Programmieren in Haskell Allgemeine Form von Typ-Deklarationen 1 2 vars :: type vars :: typecontext = > type mit vars = var1 , . . . , varn , n > 0 vari müssen im selben scope definiert sein Stefan Janssen Type signatures Programmieren in Haskell Allgemeine Form von Typ-Deklarationen 1 2 vars :: type vars :: typecontext = > type mit vars = var1 , . . . , varn , n > 0 vari müssen im selben scope definiert sein Example 1 sort :: ( Ord a ) = > [ a ] -> [ a ] Der Typcontext schränkt den Typ-Parameter a ein Stefan Janssen Typklassen Programmieren in Haskell Haskell-Klassen: In Haskell sind Typen in Klassen organisiert Jede Typklasse ist durch eine Menge von (nur) deklarierten Funktionen charakterisiert Die Klassenfunktionen werden auch Methoden genannt Typen können Mitglieder, d.h. Instanzen von Klassen werden Instanzen müssen die Methoden der Klasse implementieren Klassen können Default-Implementierungen enthalten Stefan Janssen Typklassen Beispiele für Typklassen Eq Überprüfung auf Gleichheit. In der Klasse Eq sind die Operationen == und /= definiert. Instanzen z.B.: Int, Float, Double, String Programmieren in Haskell Stefan Janssen Typklassen Beispiele für Typklassen Eq Überprüfung auf Gleichheit. In der Klasse Eq sind die Operationen == und /= definiert. Instanzen z.B.: Int, Float, Double, String Ord Ordnungsrelation. In der Klasse Ord sind die Operationen <,>,<=,>= definiert. Instanzen z.B.: Int, Float, Double, String Programmieren in Haskell Stefan Janssen Typklassen Beispiele für Typklassen Eq Überprüfung auf Gleichheit. In der Klasse Eq sind die Operationen == und /= definiert. Instanzen z.B.: Int, Float, Double, String Ord Ordnungsrelation. In der Klasse Ord sind die Operationen <,>,<=,>= definiert. Instanzen z.B.: Int, Float, Double, String Num Umfasst die numerischen Typen, z.B. Int, Float, Double Operationen: +,-,* Programmieren in Haskell Stefan Janssen Typklassen Beispiele für Typklassen Eq Überprüfung auf Gleichheit. In der Klasse Eq sind die Operationen == und /= definiert. Instanzen z.B.: Int, Float, Double, String Ord Ordnungsrelation. In der Klasse Ord sind die Operationen <,>,<=,>= definiert. Instanzen z.B.: Int, Float, Double, String Num Umfasst die numerischen Typen, z.B. Int, Float, Double Operationen: +,-,* Show Werte eines Typs, der Instanz der Klasse Show ist, lassen sich ausgeben mit show x instanzen z.B.: Int, Float, Double, String Programmieren in Haskell Stefan Janssen Vordefinierte Typklassen Programmieren in Haskell Quelle: http:// commons. wikimedia. org/wiki/ File: Classes. png, modifizierte Version aus dem Haskell 98 Report Stefan Janssen Einbau in eine Klassenhierarchie Programmieren in Haskell Stefan Janssen Eigenschaften der Klassenhierarchie Klassen-Erweiterung ist additiv - Methoden kommen hinzu. Erweiterung der Typklasse verkleinert die Menge der Instanzen. Beispiel: Komplexe Zahlen - wo würde man sie in die vordefinierte Hierarchie einbauen? Definition 1 2 3 4 class Classname Typevar where decl1 :: type11 -> ... -> type1r_1 ... decln :: typen1 -> ... -> type1r_n 5 6 7 8 def ault_func_de f1 def ault_func_de f2 ... Programmieren in Haskell Stefan Janssen Definition 1 2 3 4 class Classname Typevar where decl1 :: type11 -> ... -> type1r_1 ... decln :: typen1 -> ... -> type1r_n Programmieren in Haskell Stefan Janssen 5 6 7 8 9 10 11 12 def ault_func_de f1 def ault_func_de f2 ... class Superclass_C on te xt = > Classname Typevar where decl1 :: type11 -> ... -> type1r_1 ... decln :: typen1 -> ... -> typenr_n 13 14 15 16 def ault_func_de f1 def ault_func_de f2 ... Beispiel: EQ Programmieren in Haskell Stefan Janssen 1 2 class Eq a where (==) , (/=) :: a -> a -> Bool 3 4 5 6 -- Minimal complete definition : (==) or (/=) x == y = not ( x /= y ) x /= y = not ( x == y ) Quelle: /usr/lib/hugs/packages/hugsbase/Hugs/Prelude.hs In Worten: Ein Typ a ist Instanz der Klasse Eq, wenn auf ihm die Operationen == und /= implementiert sind. Beispiel: Ord 1 2 3 4 c l a s s ( Eq a ) => Ord a where compare : : a −> a −> O r d e r i n g ( <) , ( <=) , ( >=) , (>) : : a −> a −> Bool max , min : : a −> a −> a Programmieren in Haskell Stefan Janssen 5 6 7 8 9 10 −− M i n i m a l c o m p l e t e d e f i n i t i o n : (<=) o r compare −− u s i n g compare can be more e f f i c i e n t f o r c o m p l e x t y p e s compare x y | x==y = EQ | x<=y = LT | o t h e r w i s e = GT 11 12 13 14 15 x x x x <= < >= > y y y y = = = = compare compare compare compare = = = = y x x y 16 17 max x y 18 19 20 min x y | | | | x <= y otherwise x <= y otherwise x x x x y y y y /= == /= == GT LT LT GT Beispiel: Ord Programmieren in Haskell Stefan Janssen In Worten: Ein Typ a ist Instanz der Klasse Ord, wenn er Instanz der Klasse Eq ist, und auf ihm die Operationen <, >, <=, >= und max, min, compare implementiert sind. Rolle der Defaults Programmieren in Haskell Stefan Janssen In der Klassendefinition werden “Default”-Gleichungen für die Klassen-Methoden angegeben Die Methoden können einander gegenseitig definieren, ... ... die Instanz muss dann nur einen Teil implementieren Die Gleichungen sollten von den Implementierungen erfüllt werden,... ... was aber nicht geprüft werden kann Beispiel: Enum 1 2 3 4 5 6 7 8 c l a s s Enum a where succ , pred toEnum fromEnum enumFrom enumFromThen enumFromTo enumFromThenTo Programmieren in Haskell :: :: :: :: :: :: :: a −> a I n t −> a a −> I n t a −> [ a ] a −> a −> [ a ] a −> a −> [ a ] a −> a −> a −> [ a ] Stefan Janssen −− −− −− −− [n ..] [ n ,m . . ] [ n . . m] [ n , n ’ . . m] 9 10 11 12 13 14 15 16 −− M i n i m a l c o m p l e t e d e f i n i t i o n : toEnum , fromEnum succ = toEnum . (1+) . fromEnum pred = toEnum . s u b t r a c t 1 . fromEnum enumFrom x = map toEnum [ fromEnum x . . ] enumFromTo x y = map toEnum [ fromEnum x . . fromEnum enumFromThen x y = map toEnum [ fromEnum x , fromEnum y enumFromThenTo x y z = map toEnum [ fromEnum x , fromEnum y Quelle: /usr/lib/hugs/packages/hugsbase/Hugs/Prelude.hs Weitere Typklassen Programmieren in Haskell Vordefiniert: Es gibt viele weitere vordefinierte Typklassen Hugs gibt Auskunft per :info XX über Methoden von XX ... und Instanzen (!) ... ... aber nicht über Defaults zum Beispiel: Eq, Show, Ix, ... Selbstdefiniert: Eigene Typklassen durch neue Klassendefinitionen Eigene Instanzen durch Instanz-Deklarationen Beispiel: class (Eq a) => Group a where ... Stefan Janssen Instanzen Programmieren in Haskell Stefan Janssen 1 2 3 4 instance Classname Type where def1 ... defn Instanzen Programmieren in Haskell Stefan Janssen 1 2 3 4 5 6 7 8 instance Classname Type where def1 ... defn instance Context = > Classname Type where def1 ... defn Typ wird zur Instanz der Klasse erklärt (explizit!) Beispiel (Prelude) Programmieren in Haskell Stefan Janssen 1 2 instance Eq Char c == c ’ where = fromEnum c == fromEnum c ’ 3 4 5 instance Ord Char c <= c ’ where = fromEnum c <= fromEnum c ’ Quelle: Haskell Report Hier wird benutzt, dass die anderen Funktionen der Typklasse über Defaults definiert sind. Derived Instances Bei der Deklaration von einem Algebraischen Datentyp können mit deriving Instanzen automatisch erzeugt werden: Definition data T a1 . . . am = C1 t11 . . . t1n1 ... Cr tr 1 . . . trnr deriving (k1 , . . . , ku ) | | Programmieren in Haskell Stefan Janssen Derived Instances Bei der Deklaration von einem Algebraischen Datentyp können mit deriving Instanzen automatisch erzeugt werden: Definition data T a1 . . . am = C1 t11 . . . t1n1 ... Cr tr 1 . . . trnr deriving (k1 , . . . , ku ) aus der Prelude geht das für: Eq, Ord, Enum, Show, . . . | | Programmieren in Haskell Stefan Janssen Derived Instances Programmieren in Haskell Stefan Janssen wenn eine Typklasse eine Superklasse hat, muss der Datentyp schon Mitglied dieser Instanz sein Derived Instances Programmieren in Haskell Stefan Janssen wenn eine Typklasse eine Superklasse hat, muss der Datentyp schon Mitglied dieser Instanz sein aber die automatische Instanziierung erfüllt nicht immer DWIM Beispiel Programmieren in Haskell data Temperatur = Temp Float Einheit deriving ( Eq , Show ) Stefan Janssen Beispiel Programmieren in Haskell data Temperatur = Temp Float Einheit deriving ( Eq , Show ) Wegen deriving Eq können wir nun automatisch Temperaturen vergleichen: Temp 506 Kelvin == Temp 506 Kelvin -- = > True Stefan Janssen Beispiel Programmieren in Haskell data Temperatur = Temp Float Einheit deriving ( Eq , Show ) Wegen deriving Eq können wir nun automatisch Temperaturen vergleichen: Temp 506 Kelvin == Temp 506 Kelvin -- = > True Aber: Temp 506 Kelvin == conv ( Temp 506 Kelvin ) Fahrenheit -- = > False Stefan Janssen Wiederholung Programmieren in Haskell Stefan Janssen 1 2 3 4 5 6 conv :: Temperatur -> Einheit -> Temperatur conv ( Temp t Celsius ) Kelvin = Temp ( t + 273.15) Kelvin conv ( Temp t Kelvin ) Fahrenheit = Temp ( t *9/5 -459.67) Fahrenheit ... Eigene Instanz Programmieren in Haskell 1 2 data Temperatur = Temp Float Einheit deriving Show Stefan Janssen Eigene Instanz Programmieren in Haskell 1 2 3 4 5 6 7 8 9 10 11 12 data Temperatur = Temp Float Einheit deriving Show instance Eq Temperatur where ( Temp t Celsius ) == ( Temp u Celsius ) = t == u ( Temp t Fahrenheit ) == ( Temp u Fahrenheit ) = t == u ( Temp t Kelvin ) == ( Temp u Kelvin ) = t == u ( Temp t Kelvin ) == ( Temp u Fahrenheit ) = conv ( Temp t Kelvin ) Fahrenheit == Temp u Fahrenheit und so weiter Stefan Janssen