Funktionale Programmierung mit Haskell Dr. Michael Savorić Hohenstaufen-Gymnasium (HSG) Kaiserslautern Version 20120622
Überblick Wichtige Eigenschaften Einführungsbeispiele Listenerzeugung und Beispiel Funktionen höherer Ordnung und Beispiele (map, filter, foldr, foldl) Mergesort und Quicksort Tupel M. Savorić 2
Wichtige Eigenschaften von Haskell Es gibt keine globalen Variablen. Es gibt keine Schleifen. Ein Programm besteht aus Funktionen, die sich selbst (Rekursion) und / oder andere Funktionen aufrufen. Eine Funktion liefert bei gleichen Argumenten stets das gleiche Ergebnis (es gibt keine Seiteneffekte!). Die Datentypen Bool, Int, Integer, Float, Double, Char, String und Listen (z.b. [Integer]) sind vordefiniert, eigene Datentypen sind möglich. Kommentare bis zum Ende der Zeile werden mit -- eingeleitet. M. Savorić 3
Einführungsbeispiel 1: größter gemeinsamer Teiler (ggt) ggt :: Integer -> Integer -> Integer ggt a 0 = a ggt a b = ggt b (mod a b) Funktionsname Bemerkungen: Ergebnistyp 2. Parametertyp 1. Parametertyp Haskell geht den Quelltext von oben nach unten durch und wählt den ersten passenden Ausdruck (pattern matching) M. Savorić 4
Einführungsbeispiel 2: Länge einer Liste laenge :: [a] -> Integer laenge [] = 0 laenge (x:xs) = 1+(laenge xs) Bemerkungen: [a] ist eine Liste mit einem beliebigen Datentyp (a fungiert als Platzhalter für einen Datentyp) Listen werden in Haskell oftmals von vorne nach hinten durchlaufen, indem das erste Element einer Liste jeweils abgespalten und verarbeitet wird und anschließend die Restliste betrachtet wird M. Savorić 5
Einführungsbeispiel 3: Maximum einer Liste maxliste :: [Integer] -> Integer maxliste [x] = x maxliste (x:xs) x > max = x otherwise = max where max = maxliste xs Bemerkungen: Lokale Variablen können nach where definiert werden Verzweigungen können mit durchgeführt werden (guard) M. Savorić 6
Listenerzeugung ++ : [1,2]++[3,4] liefert [1,2,3,4] : : 1:[2,3,4] liefert [1,2,3,4] Aufzählung: [1..10] liefert [1,2,3,4,5,6,7,8,9,10] [1,3..10] liefert [1,3,5,7,9] Durch Angabe von Eigenschaften (List Comprehension): [x x <- [1..10], mod x 2 == 0] liefert [2,4,6,8,10] M. Savorić 7
Beispiel: Liste von Teilern einer Zahl Beispiel-Aufruf: teilerliste 24 liefert [1,2,3,4,6,8,12,24] teiler :: Integer -> Integer -> [Integer] teiler n m n < m = [] mod n m == 0 = [m] ++ (teiler n (m+1)) otherwise = teiler n (m+1) -- teilerliste :: Integer -> [Integer] teilerliste n = teiler n 1 M. Savorić 8
Funktionen höherer Ordnung Funktionen höherer Ordnung besitzen Funktionen als Argumente Beispiele: map: wandelt jedes Element einer Liste entsprechend einer Funktion um und speichert die jeweiligen Umwandlungsergebnisse in einer neuen Liste filter: filtert bestimmte Elemente einer Liste anhand einer speziellen Funktion (Rückgabewert Bool) und schreibt sie in eine neue Liste foldr / foldl: faltet eine Liste gemäß einer Funktion Bemerkung: map und filter und viele andere eingebaute Funktionen von Haskell sollten die Schüler im Rahmen von Übungen selbst programmieren. M. Savorić 9
map Definition: map :: (a -> b) -> [a] -> [b] Beispiel: map (\x -> x*x) [1..10] liefert [1,4,9,16,25,36,49,64,81,100] Bemerkungen: (\x -> x*x) ist eine Lambda-Definition für eine Funktion ohne Namen [1..10] erzeugt die Liste [1,2,3,4,5,6,7,8,9,10] M. Savorić 10
filter Definition: filter :: (a -> Bool) -> [a] -> [a] Beispiel: filter (\x -> mod 10 x == 0) [1..10] liefert [1,2,5,10] Bemerkungen: Für die filter-funktion sind auch Vergleiche möglich, z.b. liefert filter (<= 5) [1..10] die Liste [1,2,3,4,5] M. Savorić 11
Beispiel: Liste von Teilern einer Zahl (Version mit filter) teilerliste :: Integer -> [Integer] teilerliste n = filter (\x -> mod n x == 0) [1..n] -- prim :: Integer -> Bool prim n (length (teilerliste n)) == 2 = True otherwise = False M. Savorić 12
Faltungen Rechtsfaltung mit Startwert: foldr f s [x 1,..., x n ] = f x 1 (f x 2 (... (f x n s)...)) Ohne Startwert (foldr1) wird sofort mit dem Element x n als Startwert begonnen, d.h. (f x n-1 (f x n s)) wird ersetzt durch (f x n-1 x n ) Linksfaltung mit Startwert: foldl f s [x 1,..., x n ] = f (... (f (f s x 1 ) x 2 )...) x n Ohne Startwert (foldl1) wird sofort mit dem Element x 1 als Startwert begonnen, d.h. (f (f s x 1 ) x 2 ) wird ersetzt durch (f x 1 x 2 ) M. Savorić 13
Beispiele Summe einer Liste von Zahlen: summe :: [Integer] -> Integer summe [] = 0 summe xs = foldr (+) 0 xs Umdrehen einer Liste: umdrehen :: [Integer] -> [Integer] umdrehen [] = [] umdrehen xs = foldr (hinten_anfuegen) [] xs M. Savorić 14
Mergesort für ganze Zahlen merge :: [Integer] -> [Integer] -> [Integer] merge xs [] = xs merge [] ys = ys merge (x:xs) (y:ys) x <= y = [x] ++ (merge xs (y:ys)) x > y = [y] ++ (merge (x:xs) ys) -- mergesort :: [Integer] -> [Integer] mergesort [] = [] mergesort [x] = [x] mergesort xs = merge (mergesort (take mitte xs)) (mergesort (drop mitte xs)) where mitte = div (length xs) 2 M. Savorić 15
Quicksort für ganze Zahlen quicksort :: [Integer] -> [Integer] quicksort [] = [] quicksort [x] = [x] quicksort (x:xs) = (quicksort liste_li) ++ [x] ++ (quicksort liste_re) where liste_li = [k k <- xs, k <= x] liste_re = [k k <- xs, k > x] Problem: Die Liste xs wird zur Erzeugung der Teillisten zweimal durchlaufen. M. Savorić 16
Tupel Mehrere Werte (eventuell mit unterschiedlichen Typen) werden zu einem Tupel zusammengefasst, um so z.b. bei einer Funktion indirekt mehr als einen Wert zurückliefern zu können. Beispiel (für die verbesserte Quicksort-Version): listenaufteilung :: Integer -> [Integer] -> ([Integer],[Integer]) listenaufteilung pivot [] = ([],[]) listenaufteilung pivot (x:xs) x <= pivot = ([x] ++ (fst lt), snd lt) otherwise = (fst lt, [x] ++ (snd lt)) where lt = listenaufteilung pivot xs M. Savorić 17
Quicksort für ganze Zahlen (verbesserte Version) quicksort :: [Integer] -> [Integer] quicksort [] = [] quicksort [x] = [x] quicksort (x:xs) = (quicksort liste_li) ++ [x] ++ (quicksort liste_re) where liste_li = fst listentupel liste_re = snd listentupel listentupel = listenaufteilung x xs Bemerkungen: fst bzw. snd liefern den ersten bzw. zweiten Wert eines Tupels M. Savorić 18
Beispiele: KA-Aufgaben, komplexere Aufgaben Schreibe in Haskell eine Funktion menge, die aus einer Liste von Zahlen eine Menge von Zahlen erzeugt und zurückliefert. Hinweis: In einer Menge von Zahlen kommen keine Zahlen mehrfach vor. Als nichttriviale Teiler bezeichnet man die Menge aller Teiler einer natürlichen Zahl ohne die Zahl selbst. Ein Beispiel: Die Zahl 12 hat die nichttrivialen Teiler 1, 2, 3, 4 und 6. Eine natürliche Zahl heißt vollkommen, wenn die Summe ihrer nichttrivialen Teiler gleich der Zahl ist. Schreibe eine Funktion vollkommen_liste in Haskell, die alle vollkommenen Zahlen zwischen 1 und 1000 berechnet und in Form einer Liste zurückliefert. MIRP-Zahlen sind Primzahlen, die auch rückwärts gelesen eine Primzahl ergeben. Bestimme alle MIRP-Zahlen zwischen 1 und 1000. M. Savorić 19
Literatur Block, M., Neumann, A., Haskell Intensivkurs, Springer, 2011, ISBN 978-3-642-04717-6. Hutton, G., Programming in Haskell, Cambridge University Press, 2008, ISBN978-0-521-69269-4. M. Savorić 20