Programmieren in Haskell Wir steigen ein ... Programmieren in Haskell 1 Was wir heute machen • Umfrage: Wer hat den Hugs ausprobiert? • Ausdrücke und Werte • Datentypen • Funktionen • Aufgabe für’s Wochenende Programmieren in Haskell 2 Ausdrücke und Werte Ausdrücke repräsentieren Werte. Ausdrücke sind zum Beispiel: 42 6*7 answer reverse ".therdegmu lam raw etsiL eseiD" let { fib 0 = 1; fib 1 = 1; fib n = fib (n-2) + fib (n-1) } in fib 10 Programmieren in Haskell 3 Ausdrücke können einfach sein oder komplex. Einfach: 49 Komplex: square (3+4) Die “einfachste” Form eines Ausdrucks wird Normalform genannt. Programmieren in Haskell 4 Reduktion von Ausdrücken Reduktion, Auswertung, Vereinfachung sind synonyme Begriffe und beschreiben den Prozess, einen Ausdruck in seine einfachste Form (Normalform) zu überführen. Beispiel: Mit square :: Int -> Int square x = x * x können wir square (3+4) folgendermaßen reduzieren (das Symbol => verwenden wir, um eine Reduktion zu kennzeichnen): square (3 + 4) => square 7 (+) => 7 * 7 (square) => 49 (*) (Die rechte Spalte benennt die Funktionen, deren Definitionen in der Reduktion verwendet wurden) Programmieren in Haskell 5 Reduktionsvarianten Der obige Weg ist nicht der einzige, um den Ausdruck square (3+4) zu reduzieren. Eine Alternative ist: square (3 + 4) => => => => Programmieren in Haskell (3 + 4) * (3 + 4) (square) 7 * (3 + 4) (+) 7 * 7 (+) 49 (*) 6 Gute und schlechte Reduktionswege Gut (so wird es in Haskell gemacht): take 2 [1..] => => => => => Programmieren in Haskell take 2 (1:[2..]) 1:take 1 [2..] 1:take 1 (2:[3..]) 1:2:take 0 [3..] 1:2:[] (..) (take) (..) (take) (take) 7 Gute und schlechte Reduktionswege Gut (so wird es in Haskell gemacht): take 2 [1..] => => => => => take 2 (1:[2..]) 1:take 1 [2..] 1:take 1 (2:[3..]) 1:2:take 0 [3..] 1:2:[] (..) (take) (..) (take) (take) Schlecht (warum?): take 2 [1..] => => => => Programmieren in Haskell take 2 (1:[2..]) (..) take 2 (1:2:[3..]) (..) take 2 (1:2:3:[4..]) (..) ... 7 Datentypen Datentypen sind Mengen von Werten, zusammen mit auf diesen Werten definierten Operationen (Funktionen). “Eingebaute” Datentypen in Haskell sind zum Beispiel: • Ganze Zahlen (Integer, Int) Funktionen darauf sind z.B.: +, -, square • Fließkommazahlen (Float, Double) • Wahrheitswerte (Bool) &&, || • Listen ++, reverse, take • Zeichenketten (String) Programmieren in Haskell 8 Ausdrücke haben Typen: 42 :: Int 3 + 4 :: Int "Hallo Welt!" :: String [1,2,3] :: [Int] Programmieren in Haskell 9 Neue Datentypen Wir können auch unsere eigenen Datentypen definieren: data Einheit = Celsius | Fahrenheit | Kelvin data Temperatur = Temp Float Einheit deriving (Eq,Show) deriving (Eq,Show) Damit können wir zum Beispiel folgenden Ausdruck bilden: Temp 506 Kelvin Programmieren in Haskell 10 Funktionen auf neuen Datentypen Wir können nun zum Beispiel Einheiten umrechnen: celsius_nach_kelvin :: Temperatur -> Temperatur celsius_nach_kelvin (Temp t Celsius) = Temp (t + 273.15) Kelvin kelvin_nach_fahrenheit :: Temperatur -> Temperatur kelvin_nach_fahrenheit (Temp t Kelvin) = Temp (t*9/5-459.67) Fahrenheit Programmieren in Haskell 11 Oder etwas eleganter: umrechnen :: Temperatur -> Einheit -> Temperatur umrechnen (Temp t Celsius) Kelvin = Temp (t + 273.15) Kelvin umrechnen (Temp t Kelvin) Fahrenheit = Temp (t*9/5-459.67) Fahrenheit Programmieren in Haskell 12 Oder etwas eleganter: umrechnen :: Temperatur -> Einheit -> Temperatur umrechnen (Temp t Celsius) Kelvin = Temp (t + 273.15) Kelvin umrechnen (Temp t Kelvin) Fahrenheit = Temp (t*9/5-459.67) Fahrenheit umrechnen (Temp 506 Kelvin) Fahrenheit => Temp 451.13 Fahrenheit umrechnen (Temp (-100) Celsius) Kelvin => Temp 173.15 Kelvin Programmieren in Haskell 12 Typklassen In Haskell sind Typen in Klassen organisiert. Hier ein Ausschnitt der Klassen-Hierarchie: Enum Eq Show / \ / Ord Num / \ / \ Ix Real Fractional / Integral Typen sind Instanzen von Klassen. Programmieren in Haskell 13 Klassendefinitionen Klassendefinitionen sind Vereinbarungen von “Schnittstellen”. Zum Beispiel: Wenn ein Typ Instanz der Klasse Eq ist, kann ich mich darauf verlassen, daß ich Elemente dieses Typs auf Gleichheit testen kann. Zum Beispiel ist der Typ Int eine Instanz der Klasse Eq; ich kann also zwei Ints auf Gleichheit testen: 3 == 4 => False Und außerdem auf Ungleichheit: 3 /= 4 => True Programmieren in Haskell 14 Die Klasse Eq ist folgendermaßen definiert: class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool Wegen deriving Eq können wir automatisch auch Temperaturen vergleichen: Temp 506 Kelvin == Temp 506 Kelvin => True Programmieren in Haskell 15 Die Klasse Eq ist folgendermaßen definiert: class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool Wegen deriving Eq können wir automatisch auch Temperaturen vergleichen: Temp 506 Kelvin == Temp 506 Kelvin => True Aber: Temp 506 Kelvin == umrechnen (Temp 506 Kelvin) Fahrenheit => False Programmieren in Haskell 15 Eigene Instanzen definieren Jetzt werden wir die Gleichheit auf Temperaturen selbst definieren. Daher definieren wir den Datentyp Temperatur diesmal ohne deriving Eq: data Temperatur = Temp Float Einheit deriving Show Programmieren in Haskell 16 instance Eq Temperatur where (Temp t Celsius) == (Temp u Celsius) = (Temp t Fahrenheit) == (Temp u Fahrenheit) = (Temp t Kelvin) == (Temp u Kelvin) = (Temp t Kelvin) == (Temp u Fahrenheit) = umrechnen (Temp t Kelvin) Fahrenheit == Programmieren in Haskell t == u t == u t == u Temp u Fahrenheit 17 instance Eq Temperatur where (Temp t Celsius) == (Temp u Celsius) = (Temp t Fahrenheit) == (Temp u Fahrenheit) = (Temp t Kelvin) == (Temp u Kelvin) = (Temp t Kelvin) == (Temp u Fahrenheit) = umrechnen (Temp t Kelvin) Fahrenheit == t == u t == u t == u Temp u Fahrenheit Temp 506 Kelvin == umrechnen (Temp 506 Kelvin) Fahrenheit => True Programmieren in Haskell 17 Eine eigene Show-Instanz Temp 100 Celsius => Temp 100.0 Celsius Temp 451 Fahrenheit => Ein Film von Francois Truffaut Programmieren in Haskell 18 Eine eigene Show-Instanz Temp 100 Celsius => Temp 100.0 Celsius Temp 451 Fahrenheit => Ein Film von Francois Truffaut instance Show Temperatur where show (Temp t Fahrenheit) | round t == 451 = "Ein Film von Francois Truffaut" show (Temp t e) = "Temp " ++ show t ++ " " ++ show e Programmieren in Haskell 18 Typkontexte Oft wollen wir uns nicht auf einen Typ festlegen, aber doch Zugehörigkeit zu einer oder mehrerer Klassen verlangen. Zum Beispiel: Anstatt die Funktion max nur auf Ints zu definieren (unsere Funktion maxi vom letzten Mal) maxi :: Int -> Int -> Int maxi n m | n >= m = n | otherwise = m können wir sie für alle vergleichbaren Typen definieren (Ord a wird Typkontext genannt): max’ :: Ord a => a -> a -> a max’ n m | n >= m = n | otherwise = m Programmieren in Haskell 19 max’ 2 3 => 3 max’ "Robert" "Marc" => "Robert" max’ [1,2,3] [1,2,4] => [1,2,4] Programmieren in Haskell 20 Funktionen Funktionen bilden Eingabewerte auf Ausgabewerte ab. Als Beispiel nochmal die Quadrat-Funktion: square :: Int -> Int square x = x * x square hat ein Argument vom Typ Int (nämlich x), und das Ergebnis ist auch vom Typ Int. Man sagt: "Die Funktion square hat den Typ Int nach Int." Programmieren in Haskell 21 Funktionen können auch mehr als ein Argument haben. Zum Beispiel hat unsere Funktion max’ zwei Argumente: max’ :: Ord a => a -> a -> a max’ n m | n >= m = n | otherwise = m Man sagt: "Die Funktion max’ hat den Typ a nach a nach a (mit Kontext Ord a)." Programmieren in Haskell 22 Statt max’ 5 3 können wir auch (max’ 5) 3 schreiben. Welchen Typ hat der Ausdruck max’ 5? Programmieren in Haskell 23 Statt max’ 5 3 können wir auch (max’ 5) 3 schreiben. Welchen Typ hat der Ausdruck max’ 5? Fragen wir den Hugs: Main> :t max’ 5 max’ 5 :: (Ord a, Num a) => a -> a Programmieren in Haskell 23 Statt max’ 5 3 können wir auch (max’ 5) 3 schreiben. Welchen Typ hat der Ausdruck max’ 5? Fragen wir den Hugs: Main> :t max’ 5 max’ 5 :: (Ord a, Num a) => a -> a max’ 5 kann als neue Funktion aufgefaßt werden: max’ 5 => f where f m | 5 >= m = 5 | otherwise = m Programmieren in Haskell 23 Funktionen höherer Ordnung Funktionen sind “first-class values”, d.h. sie können Argumente anderer Funktionen sein. Dieses Prinzip wird uns durchgehend begleiten! Als Beispiel hier die Funktion map, die eine weitere Funktion auf die Elemente einer Liste anwendet: map :: (a -> b) -> [a] -> [b] map f [] = [] map f (x:xs) = f x:map f xs Programmieren in Haskell 24 Funktionen höherer Ordnung Funktionen sind “first-class values”, d.h. sie können Argumente anderer Funktionen sein. Dieses Prinzip wird uns durchgehend begleiten! Als Beispiel hier die Funktion map, die eine weitere Funktion auf die Elemente einer Liste anwendet: map :: (a -> b) -> [a] -> [b] map f [] = [] map f (x:xs) = f x:map f xs Jetzt können wir unsere Funktion max’ 5 auf eine Liste anwenden: map (max’ 5) [1,2,7,12,3,20] => [5,5,7,12,5,20] Programmieren in Haskell 24 Oder wir erhöhen jedes Element der Liste um 1: map (+1) [1,2,7,12,3,20] => [2,3,8,13,4,21] Welchen Typ hat (+1)? Programmieren in Haskell 25 Oder wir erhöhen jedes Element der Liste um 1: map (+1) [1,2,7,12,3,20] => [2,3,8,13,4,21] Welchen Typ hat (+1)? (+1) :: Num a => a -> a Programmieren in Haskell 25 Ihre Aufgabe für’s Wochenende Alle Beispiele ausprobieren, verändern, wieder ausprobieren ... Programmieren in Haskell 26