Funktionale Programmierung ALP I Funktionen höherer Ordnung Teil 2 SS 2013 Prof. Dr. Margarita Esponda Prof. Dr. Margarita Esponda Funktionale Programmierung Funktionen höherer Ordnung Nehmen wir an, wir möchten alle Zahlen innerhalb einer Liste miteinander addieren addAll:: (Num a) => [a] -> a addAll [] = 0 addAll (x:xs) = x + addAll xs oder die Und-Operation über alle Elemente einer Liste berechnen trueAll:: [Bool] -> Bool trueAll [] = True trueAll (x:xs) = x && (trueAll xs) Prof. Dr. Margarita Esponda Funktionale Programmierung Funktionen höherer Ordnung Gemeinsamkeiten von beiden Funktionen sind: 1) Binär-Operator 2) konstanter Wert, wenn die Liste leer ist. 3) gleiches Rekursions-Muster Wir können eine verallgemeinerte Funktion definieren, die beide Probleme löst Beispiel: trueAll = betweenAll (&&) True addAll = betweenAll (+) 0 multAll = betweenAll (*) 1 Verallgemeinerungen sind immer gut! Prof. Dr. Margarita Esponda Funktionale Programmierung Funktionen höherer Ordnung betweenAll :: (a -> a -> a) -> a -> [a] -> a Binäre Operation Wert der Funktion, wenn die Liste leer ist betweenAll f k [] = k betweenAll f k (x:xs) = f x (betweenAll f k xs) Prof. Dr. Margarita Esponda Funktionale Programmierung Funktionen höherer Ordnung betweenAll :: (a -> a -> a) -> a -> [a] -> a betweenAll f k [] = k betweenAll f k (x:xs) = f x (betweenAll f k xs) betweenAll f k [x1, x2,…, xn-1, xn] ⇒ f x1 (betweenAll f k [x2,…, xn-1, xn]) ⇒ f x1 (f x2 (betweenAll f k [x3,…, xn-1, xn])) ⇒ f x1 (f x2 (f x3 (betweenAll f k [x4,…, xn-1, xn]))) ... ⇒ f x1 (f x2 (f x3 ( .…. (f xn-1 (f xn (betweenAll f k [])))...) ⇒ f x1 (f x2 (f x3 (… (f xn-1 (f xn k)))…) ... ⇒ f x1 w2 Prof. Dr. Margarita Esponda Funktionale Programmierung Funktionen höherer Ordnung foldr-Funktion In Haskell ist bereits eine allgemeine Funktion vordefiniert, die Faltungs-Operator genannt wird Definition: foldr f z [] =z foldr f z (x:xs) = f x (foldr f z xs) foldr (*) 1 [1,2,3,4] : 1 1 : 2 3 2 * 3 : 4 Prof. Dr. Margarita Esponda => : * [] => * 4 * 1 24 Funktionale Programmierung Faltungs-Operatoren Beispiele: foldr (*) 1 [1..4] ⇒ (*) 1 (foldr (*) 1 [2,3,4]) ⇒ (*) 1 ((*) 2 (foldr (*) 1 [3,4])) ⇒ (*) 1 ((*) 2 ((*) 3 (foldr (*) 1 [4]))) ⇒ (*) 1 ((*) 2 ((*) 3 ((*) 4 (foldr (*) 1 [])))) ⇒ (*) 1 ((*) 2 ((*) 3 ((*) 4 1))) ⇒ (*) 1 ((*) 2 ((*) 3 4)) ⇒ (*) 1 ((*) 2 12) ⇒ (*) 1 24 ⇒ 24 Fakultät-Funktion factorial n = foldr (*) 1 [1..n] Prof. Dr. Margarita Esponda Funktionale Programmierung Funktionen höherer Ordnung Folgende Standard-Funktionen von Haskell können mit Hilfe des Faltungs-Operators definiert werden: Beispiele: sum :: (Num a) => [a] -> a sum = foldr (+) 0 product :: (Num a) => [a] product = foldr (*) 1 -> a or :: [Bool] -> Bool or = foldr (||) False or :: [Bool] -> Bool and = foldr (&&) True Prof. Dr. Margarita Esponda Funktionale Programmierung Die Natur rekursiver Funktionen Rekursive Funktionen haben oft folgende allgemeine Form: f 0 =c f (n+1) = h (f n ) Diese Art der Definitionen wird oft als Strukturelle Rekursion über die natürlichen Zahlen bezeichnet. Prof. Dr. Margarita Esponda Funktionale Programmierung Die Natur rekursiver Funktionen Eine Funktionsdefinition dieser Form über die natürlichen Zahlen sieht aus wie folgt: Sei 1+1+1+ … +1+0 = die natürliche Zahl n. Wenn wir die 0 mit c und (1+) mit h ersetzen, bekommen wir folgenden Ausdruck h(h(h(…h(h(c))…))), in dem h n-mal auf c = f(0) angewendet wird. f 0 f (n+1) Prof. Dr. Margarita Esponda = 0 = (1+) (f n) f 0 f (n+1) = c = h (f n) Funktionale Programmierung Die Natur rekursiver Funktionen Folgende Faltungsfunktion stellt eine Verallgemeinerung der Funktionen mit dieser einfachen Grundform dar: natFold :: (a->a) -> a -> Integer -> a natFold h c 0 = c natFold h c (n+1) = h (natFold h c n) Eigene Potenz-Funktion: potenz(n, m) = n m für n, m ∈Ν potenz :: Integer → Integer → Integer potenz n m = natFold (*n) 1 m Prof. Dr. Margarita Esponda Funktionale Programmierung Rekursionsarten Lineare Rekursion Rekursive Funktionen, die in jedem Zweig ihrer Definition maximal einen rekursiven Aufruf beinhalten, werden als linear rekursiv bezeichnet. Endrekursion (tail recursion) Linear rekursive Funktionen werden als endrekursive Funktionen klassifiziert, wenn der rekursive Aufruf in jedem Zweig der Definition die letzte Aktion zur Berechnung der Funktion ist. Prof. Dr. Margarita Esponda Funktionale Programmierung Funktionen höherer Ordnung foldl-Funktion Definition: foldl :: (a -> b -> a) -> a -> [b] -> a foldl f z [] =z foldl f z (x:xs) = foldl f (f z x) xs foldl f z [x1,x2, …,xn] ⇒ ⇒ foldl f (f z x1) [x2,…,xn] foldl f (f (f z x1) x2) [x3,…,xn] ... Prof. Dr. Margarita Esponda ⇒ foldl f ⇒ (f...(f (f (f z x1) x2) x3)…) (f...(f (f (f z x1) x2) x3)…) [] Funktionale Programmierung Funktionen höherer Ordnung foldl f z [] =z foldl f z (x:xs) = foldl f (f z x) xs foldl (*) 1 [8,6,4] : 8 ⇒ : 6 : 4 Prof. Dr. Margarita Esponda * [] * * z 4 6 8 ⇒ ⇒ ⇒ ⇒ foldl (*) ((*) 1 8) [6,4] foldl (*) 8 [6,4] foldl (*) ((*) 8 6) [4] foldl (*) 48 ⇒ foldl f ((*) 48 4) [] ⇒ foldl f 192 [] ⇒ 192 [4] Funktionale Programmierung Die foldl-Funktion Wichtiges Beispiel von Endrekursion foldl :: (b -> a -> b) -> b -> [a] -> b foldl f z [] = z foldl f z (x:xs) = foldl f (f z x) xs Hier werden Zwischenergebnisse akkumuliert und weitergeleitet. Mit Hilfe von Faltungs-Operatoren können sehr leicht endrekursive Funktionen definiert werden. Prof. Dr. Margarita Esponda Funktionale Programmierung Beispiele: maxi :: (Ord a) => [a] -> a maxi (x:xs) = foldl max x xs length :: [a] -> Int length xs = foldl addOne 0 xs where addOne a b = a + 1 pow :: Integer -> Integer -> Integer pow b n Prof. Dr. Margarita Esponda = foldl (*) 1 (take n [b,b..b]) Funktionale Programmierung Beispiele endrekursiver Funktionen Klassisches Beispiel einer nicht endrekursiven Definition ist: Die Standarddefinition der reverse-Funktion rev :: [a] -> [a] rev [] = [] rev (x:xs) = rev xs ++ [x] Berechnungsaufwand von rev: Reduktionen rev [x1, x2, …, xn] => rev [x2, …, xn] ++ [x1] 1 => rev [x3, …, xn] ++ [x2] ++ [x1] 1 ... => [xn] ++ [xn-1] ++ [x2] ++ [x1] 1 => [] ++ [xn] ++ … ++ [x2] ++ [x1] 1 bis hier (n+1) Reduktionen! Prof. Dr. Margarita Esponda Funktionale Programmierung Berechnungsaufwand von rev bis hier (n+1) Reduktionen! (++) :: [a] -> [a] -> [a] (++) [] ys = ys (++) (x:xs) ys = x:(xs ++ ys) Reduktionen => [] ++ [xn] ++ [xn-1] ++ … ++ [x2] ++ [x1] => [xn] ++ [xn-1] ++ … ++ [x2] ++ [x1] 1 => [xn, xn-1] ++ … ++ [x2] ++ [x1] 2 => [xn, xn-1 ,xn-2] ++ … ++ [x2] ++ [x1] 3 => . . . . => [xn, xn-1 , … ,x1] n Die gesamte Anzahl der Reduktionen ist: Prof. Dr. Margarita Esponda Quadratischer Ausführungsaufwand! Funktionale Programmierung Eine effizientere Version von rev quickRev xs = rev_helper xs [] where rev_helper [] ys = ys rev_helper (x:xs) ys = rev_helper xs (x:ys) Berechnungsaufwand: Reduktionen quickRev [x1, x2, …, xn] => rev_helper [x1,…,xn] [] 1 => rev_helper [x2,…,xn] (x1:[]) 1 n => rev_helper [x3,…,xn] (x2:x1:[]) 1 ... => (xn:, … ,x2:x1:[]) … 1 => (xn:, … ,x2:[x1]) 1 ... => (xn:, … , x3:[x2,x1]) … 1 lineare Komplexität Prof. Dr. Margarita Esponda 2n = O(n) n Funktionale Programmierung foldl f z [] =z foldl f z (x:xs) = foldl f (f z x) xs Die reverse-Funktion mit Faltungsoperator f z reverse_reloaded :: [a] -> [a] reverse_reloaded xs = foldl (flip (:)) [] xs Die flip-Funktion vertauscht die Argumente für die Funktion f Prof. Dr. Margarita Esponda flip :: (a -> b -> c) -> b -> a -> c flip f x y = f y x Funktionale Programmierung Berechnungsverlauf: reverse_reloaded [x1, x2, … , xn] => foldl (flip (:)) [] [x1, x2,…, xn] foldl.2 => foldl (flip (:)) ((flip (:)) [] x1) [x2,x3,…, xn] => foldl (flip (:)) ((:) x1 []) [x2,x3,…, xn] => foldl (flip (:)) (x1:[]) [x2,x3,…, xn] => foldl (flip (:)) [x1] [x2,x3,…, xn] foldl.2 => foldl (flip (:)) ((flip (:)) [x1] x2) [x3,…, xn] => foldl (flip (:)) ((:) x2 [x1]) [x3,…, xn] => foldl (flip (:)) (x2:[x1]) [x3,…, xn] => foldl (flip (:)) [x2, x1] [x3,…, xn] foldl.2 => . . . Prof. Dr. Margarita Esponda Funktionale Programmierung Funktionen höherer Ordnung Anwendungsbeispiel der zipWith-Funktion: Skalar-Produkt von zwei Vektoren v1 . v2 v1 = (x1, x2, .. , xn) v2 = (y1, y2, .. , yn) ist v1 . v2 = x1. y1 + x2 . y2 + .. + xn. yn skalarProd ::[Int] -> [Int] -> Int skalarProd xs ys Prof. Dr. Margarita Esponda = foldl (+) 0 (zipWith (*) xs ys) Funktionale Programmierung Funktionen höherer Ordnung Folgende Funktion berechnet die Fibonacci-Zahlen in linearer Zeit O(n) fibs :: [Integer] fibs = 0 : 1 : zipWith (+) fibs (tail fibs) fibs tail fibs zipWith (+) 0:1:1:2:3:5:8:... 1:1:2:3:5:... 1:2:3:5:8:... Anwendungsbeispiel: take 40 fibs Prof. Dr. Margarita Esponda Funktionale Programmierung Funktionen höherer Ordnung Funktionskomposition f A B g g °f (.) :: (b → c) → (a → b) → (a → c) (.) Beispiel: Prof. Dr. Margarita Esponda g f x = (g (f x)) ungerade = not . gerade C