3. Funktionales Programmieren 3.1 Grundkonzepte funktionaler Programmierung 3. Funktionales Programmieren Beispiele: Funktionsabstraktion (3) Funktionsdeklaration 3. Abstraktion über Funktionsbezeichner: Ausdruck: Abstraktion: In Haskell gibt es unterschiedliche syntaktische Formen für die Funktionsdeklaration: f (f x) \ f x -> f (f x) 1. mittels direkter Wertvereinbarung: Mit Bezeichnervereinbarung: <Funktionsbez > twice = \ f x -> f (f x) erg = ( twice sqrt) 3.0 Beispiel: twice2 = \ f -> \ x -> f (f x) erg = ( twice sqrt) 3.0 TU Kaiserslautern 3. Funktionales Programmieren = <Ausdruck von Funktionstyp > fib = \ n -> if n == 0 then 0 else if n == 1 then 1 else fib (n -1) + fib (n -2) Äquivalente Vereinbarung: ©Arnd Poetzsch-Heffter 3.1 Grundkonzepte funktionaler Programmierung 217 ©Arnd Poetzsch-Heffter 3.1 Grundkonzepte funktionaler Programmierung TU Kaiserslautern 3. Funktionales Programmieren Funktionsdeklaration (2) 218 3.1 Grundkonzepte funktionaler Programmierung Funktionsdeklaration (3) 3. mittels formalen Parametern und Fallunterscheidung über Wächtern: <Funktionsbez > <Parameterbez1 > ... | <boolscher Ausdruck > = <Ausdruck > ... | <boolscher Ausdruck > = <Ausdruck > 2. mittels einem oder mehreren formalen Parametern: <Funktionsbez > <Parameterbez1 > ... = <Ausdruck > Beispiel: Die boolschen Ausdrücke in der Deklaration heißen Wächter, engl. guards. fib n = if n == 0 then 0 else if n == 1 then 1 else fib (n -1) + fib (n -2) Beispiel: fib | | | n n == 0 = 0 n == 1 = 1 otherwise = fib (n -1) + fib (n -2) Das Schlüsselwort otherwise steht hier für True. ©Arnd Poetzsch-Heffter TU Kaiserslautern 219 ©Arnd Poetzsch-Heffter TU Kaiserslautern 220 3. Funktionales Programmieren 3.1 Grundkonzepte funktionaler Programmierung 3. Funktionales Programmieren Funktionsdeklaration (4) 3.1 Grundkonzepte funktionaler Programmierung Funktionsdeklaration (5) 4. mittels Fallunterscheidung über Mustern: <Funktionsbez > <Parametermuster > ... = ... <Funktionsbez > <Parametermuster > ... = <Ausdruck > 5. mittels Kombinationen der Formen 3 und 4. <Ausdruck > Beispiel: Muster sind ein mächtiges Programmierkonstrukt, das weiter unten genauer behandelt wird. fib fib | | Beispiel: 0 = 0 n n==1 = 1 otherwise = fib (n -1) + fib (n -2) fib 0 = 0 fib 1 = 1 fib n = fib (n -1) + fib (n -2) ©Arnd Poetzsch-Heffter TU Kaiserslautern 3. Funktionales Programmieren 221 3.1 Grundkonzepte funktionaler Programmierung TU Kaiserslautern 3. Funktionales Programmieren Bemerkungen: 222 3.1 Grundkonzepte funktionaler Programmierung Beispiel: rekursive Funktionsdekl. 1. Einzelne rekursive Funktionsdeklaration: • Jeder Funktionsdeklaration sollte die Funktionssignatur vorangestellt werden und ein Kommentar, der mindestens die Voraussetzungen an die Parameter beschreibt. rpow :: Float -> Integer -> Float -- rpow r m verlangt : m >= 0 rpow r n = if n == 0 then 1.0 else r * rpow r (n -1) Beispiel: fib :: Integer -> Integer -- fib k verlangt : k >= 0 fib 0 = 0 fib 1 = 1 fib n = fib (n -1) + fib (n -2) 2. Verschränkt rekursive Funktionsdeklaration: gerade :: Integer -> Bool ungerade :: Integer -> Bool -- Bedingung an Parameter n bei beiden Funktionen : -- n >= 0 gerade n = (n == 0) || ungerade (n -1) ungerade n = if n == 0 then False else gerade (n -1) • Die Form einer Funktionsdeklaration sollte so gewählt werden, dass die Deklaration gut lesbar ist. ©Arnd Poetzsch-Heffter ©Arnd Poetzsch-Heffter TU Kaiserslautern 223 ©Arnd Poetzsch-Heffter TU Kaiserslautern 224 3. Funktionales Programmieren 3.1 Grundkonzepte funktionaler Programmierung 3. Funktionales Programmieren Deklaration rekursiver Funktionen 3.1 Grundkonzepte funktionaler Programmierung Definition: (rekursive Funktionsdekl.) Begriffsklärung: (rekursive Definition) Eine Funktionsdeklaration heißt direkt rekursiv, wenn der definierende Ausdruck eine Anwendung der definierten Funktion enthält. Eine Definition oder Deklaration nennt man rekursiv, wenn der definierte Begriff bzw. das deklarierte Programmelement im definierenden Teil verwendet wird. Eine Menge von Funktionsdeklarationen heißt verschränkt rekursiv oder indirekt rekursiv (engl. mutually recursive), wenn die Deklarationen gegenseitig voneinander abhängen. Bemerkung: • Rekursive Definitionen finden sich in vielen Bereichen der Informatik und Mathematik, aber auch in anderen Wissenschaften und der nichtwissenschaftlichen Sprachwelt. • Wir werden hauptsächlich rekursive Funktions- und Eine Funktionsdeklaration heißt rekursiv, wenn sie direkt rekursiv ist oder Element einer Menge verschränkt rekursiver Funktionsdeklarationen ist. Datentypdeklarationen betrachten. ©Arnd Poetzsch-Heffter TU Kaiserslautern 3. Funktionales Programmieren 225 ©Arnd Poetzsch-Heffter 3.1 Grundkonzepte funktionaler Programmierung TU Kaiserslautern 3. Funktionales Programmieren 3.1 Grundkonzepte funktionaler Programmierung Begriffsklärung: (rekursive Funktion) Zur Auswertung von Funktionsanwendungen: Eine Funktion heißt rekursiv, wenn es rekursive Funktionsdeklarationen gibt, mit denen sie definiert werden kann. Sei f x = A[x] ; Eine Funktionsanwendungen f e kann nach unterschiedlichen Strategien durch Verwendung der Deklarationsgleichungen ausgewertet werden, zum Beispiel call-by-value: Bemerkungen: • Die Menge der rekursiven Funktionen ist berechnungsvollständig. • Rekursive Funktionsdeklarationen können als eine Gleichung mit einer Variablen verstanden werden, wobei die Variable von einem Funktionstyp ist: Beispiel: Gesucht ist die Funktion f , die folgende Gleichung für alle n ∈ Nat erfüllt: f n = if n = 0 then 1 else n ∗ f (n − 1) ©Arnd Poetzsch-Heffter TU Kaiserslautern 227 226 • Werte Ausdruck e aus; Ergebnis nennt man den aktuellen Parameter z . • Ersetze x in A[x] durch z . • Werte den resultierenden Ausdruck A[z] aus. Haskell benutzt die Auswertungsstrategie call-by-need (siehe 3.4). Beispiele: (Rekursion) siehe Vorlesung ©Arnd Poetzsch-Heffter TU Kaiserslautern 228 3. Funktionales Programmieren 3.1 Grundkonzepte funktionaler Programmierung 3. Funktionales Programmieren Begriffsklärung: (lineare/repetitive Rekursion) Beispiele: Vereinfachend betrachten wir hier nur Funktionsdeklarationen, bei denen die Fallunterscheidung „außen“ und die rekursiven Aufrufe in den Zweigen der Fallunterscheidung stehen. • Die übliche Definition von fac ist nicht repetitiv, da im Zweig der rekursiven Anwendung die Multiplikation an äußerster Stelle steht. • Die folgende Funktion facrep ist repetitiv: • Eine rekursive Funktionsdeklaration heißt linear rekursiv, wenn facrep :: Integer -> Integer -> Integer -- facrep n res verlangt : n >= 0 && res >= 1 facrep n res = if n == 0 then res else facrep (n -1) (res*n) in jedem Zweig der Fallunterscheidung höchstens eine rekursive Anwendung erfolgt (Beispiel: Definition von fac). • Eine rekursive Funktionsdeklaration heißt repetitiv (rekursiv), wenn sie linear rekursiv ist und die rekursiven Anwendungen in den Zweigen der Fallunterscheidung an äußerster Stelle stehen. ©Arnd Poetzsch-Heffter TU Kaiserslautern 3. Funktionales Programmieren fac n = facrep n 1 229 ©Arnd Poetzsch-Heffter 3.1 Grundkonzepte funktionaler Programmierung 230 3.1 Grundkonzepte funktionaler Programmierung Beispiel: (kaskadenartige Rekursion) • Eine rekursive Funktionsdeklaration für f heißt geschachtelt rekursiv, wenn sie Teilausdrücke der Form f (. . . f (. . . ) . . . ) enthält. • Eine rekursive Funktionsdeklaration für f heißt kaskadenartig Berechne: Wie viele Kaninchen-Pärchen leben nach n Jahren, wenn man • am Anfang mit einem neu geborenden Pärchen beginnt, • jedes neu geborene Pärchen nach zwei Jahren und dann jedes rekursiv, wenn sie Teilausdrücke der Form h(. . . f (. . . ) . . . f (. . . ) . . . ) enthält. TU Kaiserslautern TU Kaiserslautern 3. Funktionales Programmieren Begriffsklärung: (Geschachtelte Rekursion) ©Arnd Poetzsch-Heffter 3.1 Grundkonzepte funktionaler Programmierung folgende Jahr ein weiteres Pärchen Nachwuchs erzeugt und • die Kaninchen nie sterben. 231 ©Arnd Poetzsch-Heffter TU Kaiserslautern 232 3. Funktionales Programmieren 3.1 Grundkonzepte funktionaler Programmierung 3. Funktionales Programmieren Beispiel: (kaskadenartige Rekursion) (2) 3.1 Grundkonzepte funktionaler Programmierung Bemerkung: Die Anzahl der Pärchen stellen wir als Funktion ibo von n dar: • vpr dem 1. Jahr: ibo(0) = 1 • nach dem 1. Jahr: ibo(1) = 1 • nach dem 2. Jahr: ibo(2) = 2 • Aus Beschreibungssicht spielt die Form der Rekursion keine Rolle; wichtig ist eine möglichst am Problem orientierte Beschreibung. • Aus Programmierungssicht spielt Auswertungseffizienz eine • nach dem n. Jahr: wichtige Rolle, und diese hängt von der Form der Rekursion ab. Beispiel: Kaskadenartige Rekursion führt im Allg. zu einer exponentiellen Anzahl von Funktionsanwendungen (z.B. bei ibo 30 bereits 1.664.079 Anwendungen). die Anzahl aus dem Jahr vorher plus die Anzahl der im n. Jahr Geborenen; und die ist gleich der Anzahl vor zwei Jahren, also: ibo n = ibo(n − 1) + iob(n − 2) für n > 1. Insgesamt ergibt sich folgende kaskadenartige Funktionsdeklaration: ibo n = if n<=1 then 1 else ibo (n -1) + ibo (n -2) ©Arnd Poetzsch-Heffter TU Kaiserslautern 3. Funktionales Programmieren 233 ©Arnd Poetzsch-Heffter 3.1 Grundkonzepte funktionaler Programmierung TU Kaiserslautern 3. Funktionales Programmieren Unterabschnitt 3.1.3 234 3.1 Grundkonzepte funktionaler Programmierung Die Datenstruktur der Listen Eine Liste über einem Typ T ist eine total geordnete Multimenge mit Elementen aus T (bzw. eine Folge, d.h. eine Abb. Nat -> T ). Eine Liste heißt endlich, wenn sie nur endlich viele Elemente enthält. Listen und Tupel Haskell stellt standardmäßig eine Datenstruktur für Listen bereit, die bzgl. des Elementtyps parametrisiert ist. Typparameter werden üblicherweise geschrieben als a, b, ... ©Arnd Poetzsch-Heffter TU Kaiserslautern 235 ©Arnd Poetzsch-Heffter TU Kaiserslautern 236 3. Funktionales Programmieren 3.1 Grundkonzepte funktionaler Programmierung 3. Funktionales Programmieren Die Datenstruktur der Listen (2) Typ: Die Datenstruktur der Listen (3) [a] , a ist Typparameter Dem Typ [a] ist als Wertemenge die Menge aller Listen über Elementen vom Typ a zugeordnet. Funktionen: (==), (/=) (:) (++) head, last tail, init null length (!!) take, drop :: :: :: :: :: :: :: :: :: [a] → [a] → Bool a → [a] → [a] [a] → [a] → [a] [a] → a [a] → [a] [a] → Bool [a] → Int [a] → Int → a Int → [a] → [a] 3.1 Grundkonzepte funktionaler Programmierung wenn (==) auf a definiert Notation: In Haskell gibt es eine vereinfachende Notation für Listen: statt x1 : x2 : ... : xn : [] kann man schreiben: [ x1 , x2 , ..., xn ] Konstanten: [] ©Arnd Poetzsch-Heffter :: [a] TU Kaiserslautern 3. Funktionales Programmieren 237 ©Arnd Poetzsch-Heffter 3.1 Grundkonzepte funktionaler Programmierung TU Kaiserslautern 3. Funktionales Programmieren Beispiele: (Funktionen auf Listen) 238 3.1 Grundkonzepte funktionaler Programmierung Beispiele: (Funktionen auf Listen) (2) 1. Addiere alle Zahlen einer Liste von Typ [Int]: 3. Zusammenhängen zweier Listen (engl. append): mapplus :: [Int] -> Int mapplus xl = if null xl then 0 else (head xl) + mapplus (tail xl) append :: [a] -> [a] -> [a] append l1 l2 = if l1 == [] then l2 else (head l1):( append (tail l1) l2) mapplus [1 ,2 ,3 ,4 ,5 ,6] 4. Umkehren einer Liste: 2. Prüfen einer Liste von Zahlen auf Sortiertheit: ist_sortiert :: [Int] -> Bool ist_sortiert xl = if null xl || null (tail xl) then True else if (head xl)<=(head (tail xl)) then ist_sortiert (tail xl) else False ©Arnd Poetzsch-Heffter TU Kaiserslautern 239 rev :: [a] -> [a] rev xl = if null xl then [] else append (rev (tail xl)) [head xl] ©Arnd Poetzsch-Heffter TU Kaiserslautern 240