1 auf 1

Werbung
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Beispiele: Funktionsabstraktion (3)
3. Abstraktion über Funktionsbezeichner:
Ausdruck:
Abstraktion:
f (f x)
\ f x -> f (f x)
Mit Bezeichnervereinbarung:
twice = \ f x -> f (f x)
erg
= ( twice sqrt) 3.0
Äquivalente Vereinbarung:
twice2 = \ f -> \ x -> f (f x)
erg
= ( twice sqrt) 3.0
©Arnd Poetzsch-Heffter
TU Kaiserslautern
217
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Funktionsdeklaration
In Haskell gibt es unterschiedliche syntaktische Formen für die
Funktionsdeklaration:
1. mittels direkter Wertvereinbarung:
<Funktionsbez >
=
<Ausdruck von Funktionstyp >
Beispiel:
fib = \ n -> if
n == 0 then 0
else if n == 1 then 1
else fib (n -1) + fib (n -2)
©Arnd Poetzsch-Heffter
TU Kaiserslautern
218
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Funktionsdeklaration (2)
2. mittels einem oder mehreren formalen Parametern:
<Funktionsbez > <Parameterbez1 > ...
=
<Ausdruck >
Beispiel:
fib n = if
n == 0 then 0
else if n == 1 then 1
else fib (n -1) + fib (n -2)
©Arnd Poetzsch-Heffter
TU Kaiserslautern
219
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Funktionsdeklaration (3)
3. mittels formalen Parametern und Fallunterscheidung über
Wächtern:
<Funktionsbez > <Parameterbez1 > ...
| <boolscher Ausdruck > = <Ausdruck >
...
| <boolscher Ausdruck > = <Ausdruck >
Die boolschen Ausdrücke in der Deklaration heißen Wächter,
engl. guards.
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
220
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Funktionsdeklaration (4)
4. mittels Fallunterscheidung über Mustern:
<Funktionsbez > <Parametermuster > ... =
...
<Funktionsbez > <Parametermuster > ... =
<Ausdruck >
<Ausdruck >
Muster sind ein mächtiges Programmierkonstrukt, das weiter
unten genauer behandelt wird.
Beispiel:
fib 0 = 0
fib 1 = 1
fib n = fib (n -1) + fib (n -2)
©Arnd Poetzsch-Heffter
TU Kaiserslautern
221
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Funktionsdeklaration (5)
5. mittels Kombinationen der Formen 3 und 4.
Beispiel:
fib
fib
|
|
0 = 0
n
n==1
= 1
otherwise = fib (n -1) + fib (n -2)
©Arnd Poetzsch-Heffter
TU Kaiserslautern
222
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Bemerkungen:
• Jeder Funktionsdeklaration sollte die Funktionssignatur
vorangestellt werden und ein Kommentar, der mindestens die
Voraussetzungen an die Parameter beschreibt.
Beispiel:
fib :: Integer -> Integer
-- fib k verlangt : k >= 0
fib 0 = 0
fib 1 = 1
fib n = fib (n -1) + fib (n -2)
• Die Form einer Funktionsdeklaration sollte so gewählt werden,
dass die Deklaration gut lesbar ist.
©Arnd Poetzsch-Heffter
TU Kaiserslautern
223
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Beispiel: rekursive Funktionsdekl.
1. Einzelne rekursive Funktionsdeklaration:
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)
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)
©Arnd Poetzsch-Heffter
TU Kaiserslautern
224
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Deklaration rekursiver Funktionen
Begriffsklärung: (rekursive Definition)
Eine Definition oder Deklaration nennt man rekursiv, wenn der
definierte Begriff bzw. das deklarierte Programmelement im
definierenden Teil verwendet wird.
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
Datentypdeklarationen betrachten.
©Arnd Poetzsch-Heffter
TU Kaiserslautern
225
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Definition: (rekursive Funktionsdekl.)
Eine Funktionsdeklaration heißt direkt rekursiv, wenn der
definierende Ausdruck eine Anwendung der definierten Funktion
enthält.
Eine Menge von Funktionsdeklarationen heißt verschränkt rekursiv
oder indirekt rekursiv (engl. mutually recursive), wenn die
Deklarationen gegenseitig voneinander abhängen.
Eine Funktionsdeklaration heißt rekursiv, wenn sie direkt rekursiv ist
oder Element einer Menge verschränkt rekursiver
Funktionsdeklarationen ist.
©Arnd Poetzsch-Heffter
TU Kaiserslautern
226
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Begriffsklärung: (rekursive Funktion)
Eine Funktion heißt rekursiv, wenn es rekursive
Funktionsdeklarationen gibt, mit denen sie definiert werden kann.
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
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Zur Auswertung von Funktionsanwendungen:
Sei f x = A[x] ;
Eine Funktionsanwendungen f e kann nach unterschiedlichen
Strategien durch Verwendung der Deklarationsgleichungen
ausgewertet werden, zum Beispiel call-by-value:
• 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
Begriffsklärung: (lineare/repetitive Rekursion)
Vereinfachend betrachten wir hier nur Funktionsdeklarationen, bei
denen die Fallunterscheidung „außen“ und die rekursiven Aufrufe in
den Zweigen der Fallunterscheidung stehen.
• Eine rekursive Funktionsdeklaration heißt linear rekursiv, wenn
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
229
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Beispiele:
• 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:
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)
fac n = facrep n 1
©Arnd Poetzsch-Heffter
TU Kaiserslautern
230
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Begriffsklärung: (Geschachtelte 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
rekursiv, wenn sie Teilausdrücke der Form
h(. . . f (. . . ) . . . f (. . . ) . . . ) enthält.
©Arnd Poetzsch-Heffter
TU Kaiserslautern
231
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Beispiel: (kaskadenartige Rekursion)
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
folgende Jahr ein weiteres Pärchen Nachwuchs erzeugt und
• die Kaninchen nie sterben.
©Arnd Poetzsch-Heffter
TU Kaiserslautern
232
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Beispiel: (kaskadenartige Rekursion) (2)
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
• nach dem n. Jahr:
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 =
©Arnd Poetzsch-Heffter
if n<=1 then 1 else ibo (n -1) + ibo (n -2)
TU Kaiserslautern
233
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Bemerkung:
• Aus Beschreibungssicht spielt die Form der Rekursion keine Rolle;
wichtig ist eine möglichst am Problem orientierte Beschreibung.
• Aus Programmierungssicht spielt Auswertungseffizienz eine
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).
©Arnd Poetzsch-Heffter
TU Kaiserslautern
234
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Unterabschnitt 3.1.3
Listen und Tupel
©Arnd Poetzsch-Heffter
TU Kaiserslautern
235
3. Funktionales Programmieren
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.
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
236
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Die Datenstruktur der Listen (2)
Typ:
[a] , a ist Typparameter
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]
wenn (==) auf a definiert
Konstanten:
[]
©Arnd Poetzsch-Heffter
:: [a]
TU Kaiserslautern
237
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Die Datenstruktur der Listen (3)
Dem Typ [a] ist als Wertemenge die Menge aller Listen über
Elementen vom Typ a zugeordnet.
Notation:
In Haskell gibt es eine vereinfachende Notation für Listen:
statt
x1 : x2 : ... : xn : []
kann man schreiben:
[ x1 , x2 , ..., xn ]
©Arnd Poetzsch-Heffter
TU Kaiserslautern
238
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Beispiele: (Funktionen auf Listen)
1. Addiere alle Zahlen einer Liste von Typ [Int]:
mapplus :: [Int] -> Int
mapplus xl = if null xl then 0
else (head xl) + mapplus (tail xl)
mapplus [1 ,2 ,3 ,4 ,5 ,6]
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
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Beispiele: (Funktionen auf Listen) (2)
3. Zusammenhängen zweier Listen (engl. append):
append :: [a] -> [a] -> [a]
append l1 l2 = if l1 == [] then l2
else (head l1):( append (tail l1) l2)
4. Umkehren einer Liste:
rev :: [a] -> [a]
rev xl = if null xl then []
else append (rev (tail xl)) [head xl]
©Arnd Poetzsch-Heffter
TU Kaiserslautern
240
Herunterladen