Powerpoint-Präsentation

Werbung
Funktionale Programmierung
Klaus Becker
2011
2
Funktionale Programmierung
Mit Funktionen kann man programmieren. Die Programme
bestehen aus Funktionsdeklarationen.
Funktionsdeklarationen werden mit Hilfe von
Funktionskomposition, Fallunterscheidungen und
Rekursion aufgebaut.
...
Zielsetzung: Einblick in die funktionale Programmierung gewinnen

Grundideen verstehen

Grundkonzepte kennen lernen

Relevanz anhand von Miniprojekten erkennen
3
Teil 1
Warum funktional programmieren?
4
Ausgangspunkt: Maschinenebene
Registermaschinen können als einfaches Maschinenmodell angesehen werden, mit dem
wesentliche Bestandteile von Computern erfasst werden
Daten
1:
2:
3:
4:
5:
..
5
3
0
0
0
> 1
2
3
4
5
6
JMP
INC
DEC
TST
JMP
HLT
4
1
2
2
2
Programm
Registermaschine
5
Registermaschinen enthalten einen Speicher, der aus einzelnen, durchnummerierten Registern
besteht, in denen natürliche Zahlen gespeichert werden können.
0
5
1
3
2
0
...
...
> 0
1
2
3
4
5
6
TST
JMP
JMP
DEC
INC
JMP
HLT
1
3
6
1
0
0
Zur Steuerung der Registermaschinen gibt es eine einfache maschinennahe
Programmiersprache, die nur die folgenden 5 Befehle kennt:
> x INC i
Erhöhe Register i um 1. Gehe zu Zeile x+1.
> x DEC i
Erniedrige Register i um 1. Gehe zu Zeile x+1.
> x JMP i
Gehe zu Zeile i.
> x TST i
Wenn Register i ungleich 0 ist,
dann gehe zu Zeile x+1, sonst zu Zeile x+2.
> x HLT
Beende die Bearbeitung.
6
Aufgabe
(a) Kannst du auf den ersten Blick
erkennen, was das gezeigte
Registermaschinenprogramm leistet?
(b) Führe das Programm aus. Gehe von der
folgenden Anfangsbelegung der Register
aus:
{R0: 0; R1: 4; R2:6; R3:0; ...}.
Weißt du jetzt, was das Programm leistet?
Wenn nicht, dann führe weitere Tests durch.
(c) Warum ist es so schwer zu erkennen,
was das Programm leistet?
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
TST
JMP
JMP
TST
JMP
JMP
TST
JMP
HLT
TST
JMP
HLT
DEC
DEC
INC
JMP
DEC
JMP
DEC
JMP
1
3
6
2
12
9
2
18
1
16
1
2
0
0
1
9
2
6
Spaghetti-Code
7
Maschinennahe Programmiersprachen nutzen Sprungbefehle (wie z.B. JMP), um
Wiederholungen zu realisieren.
Sprungbefehle können
leicht dazu führen, dass
Programme völlig
undurchschaubar
werden. Im gezeigten
Programm führen die
Sprungbefehle zu einer
Art Spaghetti-Code, bei
dem die Ablauflogik
nicht leicht zu entwirren
ist.
Ablaufmodellierung
mit Sprungbefehlen
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
TST
JMP
JMP
TST
JMP
JMP
TST
JMP
HLT
TST
JMP
HLT
DEC
DEC
INC
JMP
DEC
JMP
DEC
JMP
1
3
6
2
12
9
2
18
1
16
1
2
0
0
1
9
2
6
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
TST
JMP
JMP
TST
JMP
JMP
TST
JMP
HLT
TST
JMP
HLT
DEC
DEC
INC
JMP
DEC
JMP
DEC
JMP
1
3
6
2
12
9
2
18
1
16
1
2
0
0
1
9
2
6
Spaghetti-Code
8
Verzicht auf Sprungbefehle
Schon früh erkannte man diese Schwäche und versucht seither, auf höheren
Programmierebenen möglichst auf die Verwendung von Sprungbefehlen zu verzichten.
Strukturierte Programmierung benutzt keine Sprungbefehle zur Ablaufmodellierung, sondern
Kontrollstrukturen, die bestimmte Ablaufmuster vorgeben.
while (R1 !=> 0) and (R2 != 0):
R0 = R0 + 1
R1 = R1 - 1
R2 = R2 - 1
while R1 <> 0:
R1 = R1 - 1
while R2 <> 0:
R2 = R2 - 1
Ablaufmodellierung
mit Kontrollstrukturen
Im Programm wird die Kontrollstruktur "Wiederholung mit Eintrittsbedingung" (dargestellt
durch eine while-Schleife) zur Modellierung benutzt.
Kontrollstrukturen erhöhen die Transparenz der Ablauflogik und verringern die Fehlerquote bei
der Entwicklung von Programmen.
9
Seiteneffekte
def invertiereBild(L):
"""
das in L dargestellte
Bild wird invertiert
d.h.: aus 0 wird 1
und aus 1 wird 0
"""
for i in range(3, len(L)):
if L[i] == 0:
L[i] = 1
else:
L[i] = 0
return L
def addBit(i, j):
"""
die Bits i, j werden add.:
0+0->0;0+1->1;1+0->1;1+1->1
"""
s = i+j
if s == 2:
s = 1
return s
def addBilder(M, N):
"""
die Bitfolgen in M und N
werden Bit für Bit addiert
"""
L = []
for i in range(len(M)):
if i < 3:
L = L + [M[i]]
else:
L = L +
[addBit(M[i], N[i])]
return L
10
Aufgabe
>>> bild = ['P1', 3, 3, 1, 1, 1, 1, 0, 1, 1, 1, 1]
>>> negativ = invertiereBild(bild)
>>> negativ
['P1', 3, 3, 0, 0, 0, 0, 1, 0, 0, 0, 0]
>>> neuesBild = addBilder(bild, negativ)
>>> neuesBild
???
Aufgabe:
Was erwartest du als Ergebnis beim Testdialog? Warum erhält man ein anderes Ergebnis?
11
Seiteneffekte
Wenn eine Funktion den Wert einer globalen
Variablen verändert, liegt ein Seiteneffekt
vor.
Seiteneffekt
zaehler = 0
def addiereZaehler(x):
global zaehler
zaehler = zaehler + 1
x = x + zaehler
return x
# Test
print(zaehler)
print(addiereZaehler(2))
print(zaehler)
Seiteneffekte führen dazu, dass das Verhalten von Funktionen schwer zu durchschauen ist.
Insbesondere bei komplexeren Programmen verliert man leicht den Überblick, welche
(beabsichtigten und auch unbeabsichtigten) Nebenwirkungen ein Funktionsaufruf mit
Seiteneffekten hat. Man versucht daher, Seiteneffekte möglichst zu vermeiden.
12
Referentielle Transparenz
Seiteneffekte können dazu führen, dass
gleiche Ausdrücke unterschiedliche Werte
haben. Ein solches Verhalten führt leicht zu
Fehlern, da man sehr genau auf die
Reihenfolge der Auswertung der
Teilausdrücke achten muss. Man versucht
daher, solche Abhängigkeit zu vermeiden.
Referentielle Transparenz liegt vor, wenn ein
Teilausdruck - unabhängig von seiner
Position innerhalb eines Ausdrucks und
unabhängig vom Zeitpunkt der Auswertung
- immer denselben Wert hat.
from zaehler import *
def tausche(x, y):
return (y, x)
# Test
print(tausche(2, 3))
print(tausche(1, 1))
print(getZaehler())
print(tausche(getZaehler(),
getZaehler()))
keine referentielle
Transparenz
13
Verzicht auf Seiteneffekte
Eine Möglichkeit besteht darin, die Verantwortung für seiteneffektfreie Programme dem
Programmentwickler zu überlassen. Dieser muss dann dafür Sorge tragen, dass keine
Bausteine mit Seiteneffekten erstellt werden.
Eine andere Möglichkeit besteht darin, die Programmiersprache so zu konzipieren, dass keine
Seiteneffekte mehr möglich sind.
Funktionale Programmierung ist (zumindest in der strengen Version) so konzipiert, dass keine
Seiteneffekte auftreten können und dass referentielle Transparenz stets gewährleistet ist.
14
Dialog über Seiteneffekte
I: Hallo! Ich vertrete hier die imperative Programmierung.
F: Hallo, und ich vertrete die funktionale Programmierung.
I: Stimmt es, dass du keine Seiteneffekte erlaubst?
F: Ja, so ist es.
I: Und wie machst du das?
F: Seiteneffekte entstehen durch Zuweisungen an globale Variablen. Bei mir gibt es gar keine Zuweisungen mehr. Dann kann
man auch nicht in die Verlegenheit kommen, einer globalen Variablen etwas zuzuweisen.
I: Klingt einfach. Aber, wie soll man dann einen so einfachen
Vorgang wie den folgenden ohne Zuweisungen beschreiben?
F: Man muss es halt ganz anders machen.
ALGORITHMUS Summenberechnung:
{vorher: n aus N}
setze s auf 0
setze i auf 0
SOLANGE i <= n:
erhöhe s um i
erhöhe i um 1
{nachher: s = 0+1+2+...+n}
I: Das kann ich mir nicht so recht vorstellen. Wenn es keine Zuweisungen gibt, dann macht es auch keinen großen Sinn,
Variablen einzuführen. Man hat ja keine Möglichkeit, den Wert der Variablen zu verändern. Und wenn man Werte von
Variablen nicht verändern kann, dann kann man es durch Anweisungen im Schleifenkörper auch nicht erreichen, dass die
Abbruchbedingung einer SOLANGE-Schleife erfüllt wird. Das macht ja alles keinen Sinn mehr.
F: Wieso bist du so auf Variablen und Schleifen fixiert?
I: Variablen benötigt man ja wohl zur Datenverwaltung und ohne Schleifen gibt es keine Wiederholungen. Oder sehe ich das
ganz falsch?
F: Ja, das siehst du etwas zu eng. Wenn du wissen willst, wie mein Berechnungskonzept funktioniert, dann musst du dir den
nächsten Abschnitt anschauen.
15
Teil 2
Funktionen als Programmierbausteine
16
Datenverarbeitung mit Funktionen
Beispiel: BMI-Berechnung
Eingabe der Daten
# Eingabe
gewicht = float(input("..: "))
groesse = float(input("..: "))
# Verarbeitung
bmi = gewicht / (groesse *
groesse)
# Ausgabe
print("BMI:", bmi)
Ausgabe der Daten
>>>
Gewicht in kg: 70
Größe in m: 1.70
BMI: 24.2214532872
Übergabe der Daten
def bmi(gewicht, groesse):
return \
gewicht/(groesse*groesse)
Rückgabe der Daten
Wir ersetzen die Eingabe von Daten (durch
einen Benutzer) durch die abstraktere
Datenübergabe und die Ausgabe der Daten
(auf dem Bildschirm) durch die abstraktere
Datenrückgabe und konzentrieren uns
stärker auf die Verarbeitung der Daten.
>>> bmi(70, 1.70)
24.221453287197235
17
Funktionale Programme
Eine Funktion ist eine Einheit, die
übergebene Daten verarbeitet und den
berechneten Funktionswert als Ergebnis
zurückgibt. Die Verarbeitung wird über eine
Funktionsdefinition (man sagt oft auch
Funktionsdeklaration) festgelegt. Aktiviert
wird eine Verarbeitung durch einen
Funktionsaufruf.
Ein funktionales Programm besteht aus
einer oder mehrerer Funktionsdefinitionen
und einem Funktionsaufruf.
Funktiondefinition
def bmi(gewicht, groesse):
return \
gewicht/(groesse*groesse)
>>> bmi(70, 1.70)
24.221453287197235
Funktionsaufruf
Eine Funktionsdefinition leistet folgende Festlegungen: die Festlegung der Datenübergabe mit
Hilfe von Parameter, die Festlegung der Datenverarbeitung und die Festlegung der
Datenrückgabe.
Bei einem Funktionsaufruf werden die Parameter mit konkreten Werten versehen. Diese Daten
werden dann nach den Festlegungen in der Funktionsdefinition verarbeitet. Abschließend wird
ein Funktionswert als Ergebnis zurückgeliefert.
18
Funktionale Programme
In einem rein funktionalen Programm dürfen in den Funktionsdefinitionen außer den
Parametern keine weiteren Variablen vorkommen.
In der Funktionsdefinition kommen dann auch keine Zuweisungen vor. Hierdurch wird
gewährleistet, dass keine Seiteneffekte zustande kommen können.
def wegberechnungen(geschwindigkeit):
return ((geschwindigkeit/10)*3,
(geschwindigkeit/10)**2,
(geschwindigkeit/10)*3 + (geschwindigkeit/10)**2)
rein funktionales Programm
Schwierigkeit:
Teilausdrücke wie (geschwindigkeit/10)*3 und (geschwindigkeit/10)**2 kommen mehrfach in
der Berechnungsvorschrift vor und müssen bei der Ausführung gegebenenfalls mehrfach
berechnet werden. Das ist ineffizient.
19
Lokale Bindungen
def wegberechnungen(geschwindigkeit):
keine (neuen) Variablen
return ((geschwindigkeit/10)*3,
(geschwindigkeit/10)**2,
(geschwindigkeit/10)*3 + (geschwindigkeit/10)**2)
def wegberechnungen(geschwindigkeit):
reaktionsweg = (geschwindigkeit/10)*3
bremsweg = (geschwindigkeit/10)**2
anhalteweg = reaktionsweg + bremsweg
return (reaktionsweg, bremsweg, anhalteweg)
nur lokale Bindungen
Ein Ausweg ergibt sich, wenn wir Variablen als lokale Konstanten verwenden. Das bedeutet,
dass Variablen nur lokal innerhalb von Funktionsdefinitionen vorkommen dürfen und dass
ihnen nur ein einziges mal ein Wert zugewiesen werden darf. Auf der rechten Seite einer
Zuweisung darf dann die Variable, die auf der linken Seite der Zuweisung steht, nicht
vorkommen. Man spricht in diesem Zusammenhang auch von lokalen Bindungen.
20
Lokale Bindungen
def wegberechnungen(geschwindigkeit):
keine (neuen) Variablen
return ((geschwindigkeit/10)*3,
(geschwindigkeit/10)**2,
(geschwindigkeit/10)*3 + (geschwindigkeit/10)**2)
def wegberechnungen(geschwindigkeit):
reaktionsweg = (geschwindigkeit/10)*3
bremsweg = (geschwindigkeit/10)**2
anhalteweg = reaktionsweg + bremsweg
return (reaktionsweg, bremsweg, anhalteweg)
nur lokale Bindungen
Mit lokalen Bindungen wird ein Term lokal (d.h. innerhalb der Funktionsdefinition) an einen
Namen gebunden, so dass man diesen Namen an Stelle des Terms benutzen kann. Funktionale
Programme mit lokalen Bindungen kann man also jederzeit in streng funktionale Programme
umwandeln, ohne das Verhalten des Programms zu verändern. Evtl. wird nur die
Berechnungszeit hierdurch beeinflusst. Funktionale Programme mit lokalen Bindungen
erzeugen also keine Seiteneffekte und sind - genau wie rein funktionale Programme referentiell transparent.
21
Funktionskomposition
Beispiel: erstes Zeichen eines Wortes ans Ende verschieben
# Eingabe
wort = input('Wort: ')
# Verarbeitung
# entferne das erste Zeichen
erstesZeichen = wort[0]
wort = wort[1:]
# füge es am Ende des Worts ein
wort = wort + erstesZeichen
# Ausgabe
print(wort)
def erstesZeichen(wort):
return wort[0]
def ohneErstesZeichen(wort):
return wort[1:]
def mitLetztemZeichen(wort, \
zeichen):
return wort + zeichen
mehrere Aktionen hintereinander
ausführen
Funktionsaufrufe ineinander schachteln
>>> mitLetztemZeichen(ohneErstesZeichen('TOR'), erstesZeichen('TOR'))
'OR'
'ORT'
'T'
22
Funktionskomposition
Bei der Komposition
Beispiel:
Zeichen innerhalb
bzw. Verkettung
eines Wortes
vonverschieben
Funktionen werden Funktionen
ineinandergeschachtelt aufgerufen.
def erstesZeichen(wort):
return wort[0]
def ohneErstesZeichen(wort):
return wort[1:]
def mitLetztemZeichen(wort, zeichen):
return wort + zeichen
def erstesZeichenAlsLetztesZeichen(wort):
return mitLetztemZeichen(ohneErstesZeichen(wort), \
erstesZeichen(wort))
Aufgabe: Löse die Probleme in (a) und (b) analog mit einer Funktion, die geeignete
Hilfsfunktionen benutzt.
(a) In einer nicht-leeren Zeichenkette soll das letzte Element ganz an an den Anfang der
Zeichenkette gesetzt werden. Aus 'ORT' soll so 'TOR' erzeugt werden.
(b) In einer nicht-leeren Zeichenkette soll das erste mit dem letzten Zeichen ausgetauscht
werden. Aus 'TOR' soll so 'ROT' erzeugt werden.
23
Fallunterscheidungen
Bei einer Funktionsdefinition mit Fallunterscheidung werden die Funktionswerte für
verschiedene Fälle (evtl. unterschiedlich) festgelegt.
Beispiel: erstes Zeichen eines Wortes entfernen
WENN das Wort Zeichen enthält:
entferne das erste Zeichen
SONST:
tue nichts
def ohneErstesZeichen(wort):
if wort != '':
return wort[1:]
else:
return wort
Fallunterscheidung
Funktionsdefinition mit
Fallunterscheidung
Aufgabe: Löse die folgenden Probleme mit geeigneten Hilfsfunktionen. Dabei soll auch
berücksichtigt werden, dass das Ausgangswort eventuell keine Zeichen enthält.
(a) In einer nicht-leeren Zeichenkette soll das letzte Element ganz an an den Anfang der
Zeichenkette gesetzt werden. Aus 'ORT' soll so 'TOR' erzeugt werden.
(b) In einer nicht-leeren Zeichenkette soll das erste mit dem letzten Zeichen ausgetauscht
werden. Aus 'TOR' soll so 'ROT' erzeugt werden.
24
Wiederholungen
Beispiel: Wörter umdrehen
{neuesWort -> ''; altesWort -> 'LEBEN'}
neuesWort = altesWort[0] + neuesWort
altesWort = altesWort[1:]
{neuesWort -> 'L'; altesWort -> 'EBEN'}
neuesWort = altesWort[0] + neuesWort
altesWort = altesWort[1:]
{neuesWort -> 'EL'; altesWort -> 'BEN'}
neuesWort = altesWort[0] + neuesWort
altesWort = altesWort[1:]
{neuesWort -> 'BEL'; altesWort -> 'EN'}
neuesWort = altesWort[0] + neuesWort
altesWort = altesWort[1:]
{neuesWort -> 'EBEL'; altesWort -> 'N'}
neuesWort = altesWort[0] + neuesWort
altesWort = altesWort[1:]
{neuesWort -> 'NEBEL'; altesWort -> ''}
iterativer Lösungsansatz
(benutzt Variablen und eine Schleife)
Eingabe: wort
altesWort = wort
neuesWort = ''
SOLANGE altesWort noch Zeichen enthält:
füge das erste Zeichen von altesWort vorne in neuesWort ein
entferne das erste Zeichen von altesWort
Ausgabe: neuesWort
25
Rekursion
Beispiel: Wörter umdrehen
umdrehen('LEBEN') ->
(umdrehen('EBEN') + 'L') ->
((umdrehen('BEN') + 'E') + 'L') ->
(((umdrehen('EN') + 'B') + 'E') + 'L') ->
((((umdrehen('N') + 'E') + 'B') + 'E') + 'L') ->
(((((umdrehen('') + 'N') + 'E') + 'B') + 'E') + 'L') ->
((((('' + 'N') + 'E') + 'B') + 'E') + 'L') ->
(((('N' + 'E') + 'B') + 'E') + 'L') ->
((('NE' + 'B') + 'E') + 'L') ->
(('NEB' + 'E') + 'L') ->
('NEBE' + 'L') ->
'NEBEL'
rekursiver Lösungsansatz
(benutzt Problemreduktionen und
Funktionskomposition)
Rekursion
26
Beispiel: Wörter umdrehen
Problemreduktionsmuster:
umdrehen('LEBEN') ->
(umdrehen('EBEN') + 'L') ->
((umdrehen('BEN') + 'E') + 'L') ->
(((umdrehen('EN') + 'B') + 'E') + 'L') ->
((((umdrehen('N') + 'E') + 'B') + 'E') + 'L') ->
(((((umdrehen('') + 'N') + 'E') + 'B') + 'E') + 'L') ->
((((('' + 'N') + 'E') + 'B') + 'E') + 'L') ->
(((('N' + 'E') + 'B') + 'E') + 'L') ->
((('NE' + 'B') + 'E') + 'L') ->
(('NEB' + 'E') + 'L') ->
Fall 1: wort == ''
umdrehen(wort) -> wort
Fall 2: wort != ''
umdrehen(wort) -> umdrehen(wort[1:]) + wort[0]
('NEBE' + 'L') ->
'NEBEL'
rekursive Funktionsdefinition
def umdrehen(wort):
if len(wort) == 0:
return wort
else:
return umdrehen(wort[1:]) + wort[0]
27
Rekursion
Rekursive Problemreduktion ist eine Problemlösestrategie, bei der ein Problem auf ein
strukturgleiches Problem (in "verkleinerter" Form) zurückgeführt wird.
Eine rekursive Funktionsdefinition ist eine Funktionsdefinition, bei der die Funktion selbst im
Funktionsterm benutzt wird.
def umdrehen(wort):
if len(wort) == 0:
return wort
else:
return umdrehen(wort[1:]) + wort[0]
28
Ausführung rekursiver Funktionsdef.
umdrehen('LEBEN') ->
(umdrehen('EBEN') + 'L') ->
((umdrehen('BEN') + 'E') + 'L') ->
(((umdrehen('EN') + 'B') + 'E') + 'L') ->
((((umdrehen('N') + 'E') + 'B') + 'E') + 'L') ->
(((((umdrehen('') + 'N') + 'E') + 'B') + 'E') + 'L') ->
((((('' + 'N') + 'E') + 'B') + 'E') + 'L') ->
(((('N' + 'E') + 'B') + 'E') + 'L') ->
((('NE' + 'B') + 'E') + 'L') ->
(('NEB' + 'E') + 'L') ->
('NEBE' + 'L') ->
'NEBEL'
Bei der Auswertung von Funktionsaufrufen kommt es bei rekursiven Funktionsdefinitionen zur
wiederholten Ausführung strukturgleicher Reduktionsschritte. Rekursion kann somit als Ersatz
für die Kontrollstruktur "Wiederholung" benutzt werden.
Damit dieses Wiederholungskonzept terminiert, muss nach endlichen vielen
Reduktionsschritten eine Situation erreicht werden, bei der die Lösung zum Problem direkt
angegeben werden kann. Man versucht daher, Reduktionsschritte so zu konzipieren, dass sie
das Problem in einer Weise "verkleinern", die nur endlich viele Verkleinerungsschritte zulässt.
Rekursion ist eine mächtige und gerne genutzte Strategie beim Problemlösen. Wenn der
Problemkontext es zulässt, dann kann man das Problemlöseverfahren sehr einfach und
kompakt über eine Problemreduktion beschreiben. Die wiederholte Ausführung der
Reduktionsschritte - und damit die Erzeugung der Lösung - überlässt man dem Ausführsystem.
29
Modularisierung
Fazit:
Funktionale Programmierung setzt konsequent das Modularisierungsprinzip um.
Modularisierung bedeutet, ein Gesamtsystem nach dem Baukastenprinzip aus Einzelbausteinen
(den sogenannten "Modulen") zusammenzusetzen. Funktionen können als Bausteine
angesehen werden, die man mit Hilfe von Funktionskomposition flexibel zusammensetzen
kann. Wenn sie - wie in der funktionalen Programmierung üblich - frei von Seiteneffekten
konzipiert werden, dann können die Bausteine unabhängig genutzt werden.
Aufgaben
30
Aufgabe:
Wir betrachten die Berechnung der Summe natürlicher Zahlen.
ALGORITHMUS Summenberechnung:
{vorher: n aus N}
setze s auf 0
setze i auf 0
SOLANGE i <= n:
erhöhe s um i
erhöhe i um 1
{nachher: s = 0+1+2+...+n}
iterative Lösung
Regeln Summenberechnung:
summe(0) ->
0
summe(5) ->
5 + summe(4)
rekursive Lösung
Entwickle und teste eine hierzu passende Funktionsdefinition.
Aufgaben
31
Aufgabe:
Ein Kapital von 1000 Euro wird jährlich mit 5% verzinst. Die Funktion kapital(n) beschreibe den
Kapitalwert nach n Jahren.
Die folgenden Problemreduktionsschritte sollen einem funktionalen Programm zu Grunde
liegen.
Verallgeinere diese Reduktionsschritte zu einem Programm und teste es mit mehreren
Funktionsaufrufen.
kapital(0) ->
1000
kapital(5) ->
kapital(4) + 0.05 * kapital(4)
Aufgaben
32
Aufgabe:
Ein Patient nimmt jeden Morgen 5 mg eines Medikaments ein. Im Laufe des Tages werden von
dem gesamten, im Körper befindlichen Medikament 40% abgebaut. Die Funktion
medikamentenmenge(n) beschreibe die Menge des Medikaments (in mg), die sich am n-ten
Tag morgens nach Einnahme des Medikaments im Körper befindet.
Ergänze die folgenden Problemreduktionsschritte und verallgemeinere sie zu einem
funktionalen Programm.
medikamentenmenge(0) ->
...
medikamentenmenge(5) ->
...
33
Aufgaben
Aufgabe:
Was leistet die folgendermaßen definierte Funktion?
Teste diese Funktion mit verschiedenen Aufrufen und erkläre, wie die Ergebnisse zustande
kommen.
def anzahl(element, liste):
if len(liste) == 0:
return 0
else:
if liste[0] == element:
return (1 + anzahl(element, liste[1:]))
else:
return anzahl(element, liste[1:])
34
Aufgaben
Aufgabe:
Das Problem besteht darin, ein Element aus einer Liste zu entfernen. Kommt das Element
mehrfach vor, so soll nur das erste / alle vorkommende Element entfernt werden. Die
folgenden Problemreduktionsschritte sollen den funktionalen Programmen zu Grunde liegen.
entferneErstes('b', []) ->
[]
entferneErstes('b', ['b', 'c', 'b']) ->
['c', 'b']
entferneErstes('b', ['a', 'd', 'b', 'c', 'b']) ->
['a'] + entferneErstes('b', ['d', 'b', 'c', 'b'])
entferneAlle('b', []) ->
entferneAlle('b', ['b', 'c', 'b']) ->
entferneAlle('b', ['a', 'd', 'b', 'c', 'b']) ->
35
Aufgaben
Aufgabe:
Das Problem besteht darin, jedes Element aus einer Zahleniste um einen bestimmten Wert zu
erhöhen.
Ergänze die folgenden Problemreduktionsschritte und verallgemeinere sie zu einem
funktionalen Programm.
addiere(3, []) ->
addiere(3, [4, 6, 6, 2, 0, 3]) ->
Aufgabe:
Das Problem besteht darin, aus einer Zahlenliste alle Elemente herauszufiltern, die kleiner als
ein bestimmter Wert sind.
Erstelle erst einmal geeignete Problemreduktionsschritte. Verallgemeinere sie anschließend zu
einem funktionalen Programm.
36
Aufgaben
def invertiereBild(L):
"""
das in L dargestellte
Bild wird invertiert
d.h.: aus 0 wird 1
und aus 1 wird 0
"""
for i in range(3, len(L)):
if L[i] == 0:
L[i] = 1
else:
L[i] = 0
return L
Aufgabe: Das Funktionsdefinitionen zur
Bildverarbeitung sind noch keine
Funktionsdefinitionen im Sinne der
funktionalen Programmierung. Warum?
Schreibe sie so um, dass keine
Wiederholungsanweisungen mehr
vorkommen und Variablen nur für lokale
Bindungen benutzt werden.
def addBit(i, j):
"""
die Bits i, j werden add.:
0+0->0;0+1->1;1+0->1;1+1->1
"""
s = i+j
if s == 2:
s = 1
return s
def addBilder(M, N):
"""
die Bitfolgen in M und N
werden Bit für Bit addiert
"""
L = []
for i in range(len(M)):
if i < 3:
L = L + [M[i]]
else:
L = L +
[addBit(M[i], N[i])]
return L
37
Teil 3
Funktionen als Datenobjekte
38
Datensätze sortieren
Im Sportverein werden die Daten der Mitglieder mit einem Programm verwaltet. Für jedes
Mitglied liegt ein Datensatz vor, der z.B. Vorname, Nachname, Geburtsdatum etc. enthält.
Programme zur Verwaltung solcher Daten bieten in der Regel eine Sortierfunktion an. Mit Hilfe
der Sortierfunktion können die Daten für bestimmte Zwecke umsortiert werden, z.B. nach
Name und Vorname oder nach dem Geburtsdatum.
39
Datensätze sortieren
Es gibt eine Reihe von Sortierverfahren zur Realisierung der Sortierfunktion. Wir betrachten im
Folgenden das Sortieren durch Einfügen.
def einfuegen(e, L):
if len(L) == 0:
return [e]
else:
if e < L[0]:
return [e] + L
else:
return [L[0]] + einfuegen(e, L[1:])
def sortieren(L):
if len(L) == 0:
return L
else:
return einfuegen(L[0], sortieren(L[1:]))
Sortieren durch Einfügen
40
Datensätze sortieren
def einfuegen(e, L):
Alphabetische Sortierung nach
if len(L) == 0:
return [e]
dem Nachnamen
else:
if e[1] < L[0][1]:
personendaten = [
return [e] + L
('Britta', 'Koch', (23,5,1995)),
else:
('Paul', 'Schwarz', (11,11,2003)),
return [L[0]] + einfuegen(e, L[1:])
('Silke', 'Schmidt', (13,7,1990)),
('Jennifer', 'Abel', (15,12,1993)),
def sortieren(L):
('Adriana', 'Müller', (21,1,2001)),
if len(L) == 0:
('Tobias', 'Klein', (1,3,1997)),
return L
('Philipp', 'Bach', (21,4,1997)),
else:
('Oliver', 'Schmitt', (14,4,1994)),
return einfuegen(L[0], sortieren(L[1:]))
('Simone', 'Schuster', (20,12,2000)),
('Pia', 'Schwarz', (11,11,2003)),
def einfuegen(e, L):
('Andreas', 'Beyer', (16,3,1988)),
if len(L) == 0:
('Alexander', 'Heinrich', (17,3,1999)),
return [e]
('Maximilian', 'Meyer', (21,6,1986))]
else:
if e[1] < L[0][1] or (e[1] == L[0][1] and e[0] < L[0][0]):
return [e] + L
else:
return [L[0]] + einfuegen(e, L[1:])
Alphabetische Sortierung nach
def sortieren(L):
if len(L) == 0:
return L
else:
return einfuegen(L[0], sortieren(L[1:]))
dem Nach- und Vornamen
Alphabetische Sortierung nach
dem Geburtsdatum?
41
Flexible Vergleiche
Die Black-Box-Modellierungen zeigen Situationen, in denen eine Vergleichsoperation an die
Funktion übergeben wird. In der oberen Abbildung wird als Vergleichsoperation der Vergleich
der Nachnamen übergeben, in der unteren Abbildung ein Vergleich der Geburtsdaten.
einfuegen
('Pia', 'Schwarz', (11,11,2003))
e
[('Andreas', 'Beyer', (16,3,1988)), ...]
L
kleinerNachname
vergleich
[
('Andreas', 'Beyer', ...),
...,
('Pia', 'Schwarz', ...),
...
]
einfuegen
('Pia', 'Schwarz', (11,11,2003))
e
[('Andreas', 'Beyer', (16,3,1988)), ...]
L
juenger
vergleich
[
('Pia', 'Schwarz', (11,11,2003)),
('Andreas', 'Beyer', (16,3,1988)),
...,
]
42
Ein Parameter für Vergleichsfunktionen
Funktionale Programmierung ermöglicht solche Situationen, in denen einer Funktion Daten
übergeben werden können, die selbst eine Funktion darstellen.
einfuegen
('Pia', 'Schwarz', (11,11,2003))
e
[('Andreas', 'Beyer', (16,3,1988)), ...]
L
kleinerNachname
vergleich
[
('Andreas', 'Beyer', ...),
...,
('Pia', 'Schwarz', ...),
...
]
def einfuegen(e, L, vergleich):
if len(L) == 0:
return [e]
else:
if vergleich(e, L[0]) == True:
return [e] + L
else:
return [L[0]] + einfuegen(e, L[1:], vergleich)
def sortieren(L, vergleich):
if len(L) == 0:
return L
else:
return einfuegen(L[0], sortieren(L[1:], vergleich), vergleich)
43
Funktionsaufrufe
def einfuegen(e, L, vergleich):
if len(L) == 0:
return [e]
else:
if vergleich(e, L[0]) == True:
# Test
return [e] + L
else:
personendaten
= vergleich)
[
return [L[0]] + einfuegen(e,
L[1:],
('Jennifer', 'Abel', (15,12,1993)),
('Philipp', 'Bach', (21,4,1997)),
def sortieren(L, vergleich):
('Andreas', 'Beyer', (16,3,1988)),
if len(L) == 0:
('Alexander', 'Heinrich', (17,3,1999)),
return L
('Tobias', 'Klein', (1,3,1997)),
else:
('Britta', 'Koch',
(23,5,1995)),
return einfuegen(L[0], sortieren(L[1:],
vergleich),
vergleich)
('Maximilian', 'Meyer', (21,6,1986)),
('Adriana', 'Müller', (21,1,2001)),
('Silke', 'Schmidt', (13,7,1990)),
('Oliver', 'Schmitt', (14,4,1994)),
('Simone', 'Schuster', (20,12,2000)),
('Pia', 'Schwarz', (11,11,2003)),
('Paul', 'Schwarz', (11,11,2003))]
# Situation 1
def kleinerAlphabetischNachname(person1, person2):
if person1[1] < person2[1]:
return True
else:
return False
print(sortieren(personendaten, kleinerAlphabetischNachname))
44
Funktionsaufrufe
# Test
personendaten = [...]
# Situation 2
def geburtsdatum(person):
return person[2]
def tag(datum):
return datum[0]
def monat(datum):
return datum[1]
def jahr(datum):
return datum[2]
def kleinerDatum(datum1, datum2):
if jahr(datum1) < jahr(datum2) or \
jahr(datum1) == jahr(datum2) and monat(datum1) < monat(datum2) or \
jahr(datum1) == jahr(datum2) and monat(datum1) == monat(datum2) and \
tag(datum1) < tag(datum2):
return True
else:
return False
def juenger(person1, person2):
return kleinerDatum(geburtsdatum(person2), geburtsdatum(person1))
print(sortieren(personendaten, juenger))
45
Funktionen höherer Ordnung
Eine Funktion höherer Ordnung ist eine Funktion, die Funktionen als Übergabedaten erhält
oder eine Funktion als Rückgabedatum zurückliefert.
def rechnen(f, a, b):
return f(a, b)
def add(a, b):
return a + b
def sub(a, b):
return a - b
>>> rechnen(add, 3, 6)
9
>>> rechnen(sub, 5, 2)
3
46
Aufgaben
Aufgabe:
(a) Kann man die Funktion rechnen auch zur Ausführung von Vergleichsoperationen
benutzen? Führe hierzu geeignete Tests durch.
(b) Die Funktion anwenden ist folgendermaßen definiert. Teste die Funktion anwenden mit
verschiedenen konkreten Funktionen.
def anwenden(f, a):
return f(a)
47
Funktion auf Listenelemente anwenden
# Berechnungsmodell 1
def beitraegeBerechnen1(personenListe):
if personenListe == []:
return personenListe
else:
(vorname, name, geburtsdatum) = personenListe[0]
aktuellesJahr = 2010
alter = aktuellesJahr - geburtsdatum[2]
if alter < 10:
beitrag = 20
elif alter < 20:
beitrag = 40
else:
beitrag = 60
personNeu = (vorname, name, geburtsdatum, beitrag)
return [personNeu] + beitraegeBerechnen2(personenListe[1:])
# Anwendung
personendaten = [('Jennifer', 'Abel', (15,12,1993)), ...]
print(beitraegeBerechnen1(personendaten))
Im Sportverein steht die Berechnung des jährlichen Mitgliedsbeitrags an. Zwei
Beitragberechnungsmodelle sollen durchgespielt werden. Im ersten Modell ist der Beitrag nur
grob gestaffelt: Kinder, Jugendliche und Erwachsene zahlen jeweils einen festen Betrag. Im
zweiten Modell ist der Beitrag direkt an das Alter (am Ende des Kalenderjahres) gekoppelt.
48
Funktion auf Listenelemente anwenden
# Berechnungsmodell 2
def beitraegeBerechnen2(personenListe):
if personenListe == []:
return personenListe
else:
(vorname, name, geburtsdatum) = personenListe[0]
aktuellesJahr = 2010
alter = aktuellesJahr - geburtsdatum[2]
beitrag = 3 * alter
personNeu = (vorname, name, geburtsdatum, beitrag)
return [personNeu] + beitraegeBerechnen1(personenListe[1:])
# Anwendung
personendaten = [('Jennifer', 'Abel', (15,12,1993)), ...]
print(beitraegeBerechnen2(personendaten))
Aufgabe
(a) Teste erst einmal die beiden Funktionen. Entwickle selbst ein weiteres Berechnungsmodell
und die passende Berechnungsfunktion.
(b) Vergleiche die Funktionsdefinitionen. Welche Gemeinsamkeiten fallen auf?
(c) Siehst du eine Möglichkeit, eine Funktion höherer Ordnung zu benutzen, um die
gemeinsame Berechnungsstruktur zu erfassen?
49
Der map-Operator
Verarbeitungssituation: Jedes Element einer Liste wird mit der gleichen Vorschrift verarbeitet
und durch das verarbeitete Element ersetzt.
Wir benutzen den sogenannen map-Operator zur Erfassung dieser Verarbeitungssituation.
Beispiele zur Anwendung des map-Operators:
50
Der map-Operator
def myMap(liste, f):
if liste == []:
return liste
else:
return [f(liste[0])] + myMap(liste[1:], f)
personendaten = [('Jennifer', 'Abel', (15,12,1993)),...]
def beitragBerechnen1(person):
(vorname, name, geburtsdatum) = person
aktuellesJahr = 2010
alter = aktuellesJahr - geburtsdatum[2]
if alter < 10:
beitrag = 20
elif alter < 20:
beitrag = 40
else:
beitrag = 60
personNeu = (vorname, name, geburtsdatum, beitrag)
return personNeu
print(myMap(personendaten, beitragBerechnen1))
def myMap(liste, f):
return [f(x) for x in liste]
51
Listenelemente herausfiltern
def erwachsenHerausfiltern(liste):
if liste == []:
return liste
else:
person = liste[0]
geburtsdatum = person[2]
aktuellesJahr = 2010
alter = aktuellesJahr - geburtsdatum[2]
if alter >= 18:
return [liste[0]] + erwachsenHerausfiltern(liste[1:])
else:
return erwachsenHerausfiltern(liste[1:])
# Test
personendaten = [('Jennifer', 'Abel', (15,12,1993)), ...]
# Anfrage 1
print(erwachsenHerausfiltern(personendaten))
Die Liste der Datensätze zur Verwaltung der Mitglieder des Sportvereins soll nach
verschiedenen Kriterien durchsucht werden: Wer gilt als erwachsen (d.h. ist am Ende des
Jahres mindestens 18 Jahre alt)? Wer hat im Februar Geburtstag? Wer hat einen Nachnamen,
der mit S beginnt? Es sollen alle Datensätze, die das jeweilige Suchkriterium erfüllen, ermittelt
werden.
52
Listenelemente herausfiltern
def erwachsenHerausfiltern(liste):
if liste == []:
return liste
else:
person = liste[0]
geburtsdatum = person[2]
aktuellesJahr = 2010
alter = aktuellesJahr - geburtsdatum[2]
if alter >= 18:
return [liste[0]] + erwachsenHerausfiltern(liste[1:])
else:
return erwachsenHerausfiltern(liste[1:])
# Test
personendaten = [('Jennifer', 'Abel', (15,12,1993)), ...]
# Anfrage 1
print(erwachsenHerausfiltern(personendaten))
Aufgabe
(a) Entwickle entsprechende Funktionsdefinitionen, um die Datensätze zu den beiden anderen
oben genannten Kriterien herauszufiltern.
(b) Beschreibe das Schema, das all diesen Filteroperationen zu Grunde liegt.
(c) Siehst du eine Möglichkeit, eine Funktion höherer Ordnung zu benutzen, um das
gemeinsame Berechnungsschema zu erfassen?
53
Der filter-Operator
Verarbeitungssituation: Elemente einer Liste, die eine bestimmte Bedingung erfüllen, werden in
einer neuen Liste zusammengestellt.
Wir benutzen den sogenannen filter-Operator zur Erfassung dieser Verarbeitungssituation.
Beispiele zur Anwendung des filter-Operators:
54
Der filter-Operator
def myFilter(liste, f):
def myFilter(liste, f):
if liste == []:
return [x for x in liste if f(x)]
return liste
else:
if f(liste[0]):
return [liste[0]] + myFilter(liste[1:], f)
else:
return myFilter(liste[1:], f)
personendaten = [('Jennifer', 'Abel', (15,12,1993)),...]
def erwachsen(person):
geburtsdatum = person[2]
aktuellesJahr = 2010
alter = aktuellesJahr - geburtsdatum[2]
if alter >= 18:
return True
else:
return False
# Anfrage 1
print(myFilter(personendaten, erwachsen))
55
Teil 4
Deklarative Programmierung
56
Dialog über Datenverarbeitung
I: Hallo! Ich vertrete hier die imperative Programmierung.
F: Hallo, und ich vertrete die funktionale Programmierung.
I: Neulich hast du behauptet, dass man Programme ohne Variablen und
Zuweisungen schreiben kann und dass man keine Schleifen benötigt.
F: Das stimmt - zumindest so ungefähr!
I: Ich kann mir das immer noch nicht so recht vorstellen. Wie soll das z.B. bei
einem ganz einfachen Algorithmus wie dem folgenden funktionieren?
F: Umdenken ist die Devise! Der Algorithmus benutzt Befehle, um die Daten zu
verarbeiten. Die Daten werden dabei mit Hilfe von Variablen verwaltet. Ich
mache das ganz anders. Ich konzipiere eine Funktion, die das Gewünschte
leistet, und definiere sie mit Hilfe von Regeln.
I: Sehe ich das richtig, dass da auch Variablen vorkommen?
F: Ja, aber nur als Parameter, um Daten an die Funktion zu übergeben. Der
entscheidende Unterschied ist, dass sie ihre Werte nicht verändern.
I: Das ist ja alles schön und gut. Aber, wozu soll das Ganze gut sein? Ich
komme mit meiner Methode doch auch zum Ziel.
F: Stimmt, wir kommen beide zum Ziel, aber mit ganz unterschiedlichen
Methoden. Du benutzt Algorithmen, die letztlich aus Anweisungen bzw.
Befehlen bestehen. Diese Befehle richten sich an eine tatsächliche oder
gedachte Maschine. Bei der Entwicklung von Algorithmen musst du also denken
wie eine Maschine.
I: Das habe ich zwar noch nie so empfunden, aber eigentlich hast du recht. Und
wie denkst du?
ALGORITHMUS Summenberechnung:
{vorher: n aus N}
setze s auf 0
setze i auf 0
SOLANGE i <= n:
erhöhe s um i
erhöhe i um 1
{nachher: s = 0+1+2+...+n}
FUNKTION summe:
{Fall 1:}
n == 0: summe(n)->0
{Fall 2:}
n > 0: summe(n)->n+summe(n-1)
57
Dialog über Datenverarbeitung
F: Ich entwickle Beschreibungen anstatt Befehle zu erteilen. Ich beschreibe das
gewünschte Berechnungsverhalten mit einer Funktion, die den Ausgangsdaten
die Zieldaten zuordnet. Anschließend lege ich mit Hilfe von Regeln die
Zuordnung präzise fest.
I: Das erinnert mich irgendwie an Funktionsdefinitionen in der Mathematik.
F: Ja, da macht man das ganz genauso.
I: Und warum sollen wir beim Programmieren vorgehen wie Mathematiker? Hat
das irgendwelche Vorteile?
F: Beim Programmieren macht man leicht Fehler. Gib zu, dass du auch schon
mal die Anweisung "erhöhe i um 1" vergessen hast. Wenn man funktional
programmiert, kann man sich leichter von der Korrektheit der Programme
überzeugen. Ich mache mir die Korrektheit von Regeln immer anhand typischer
Beispiele klar:
I: Ok, das sehe ich ein! Aber, wenn ich mich so umschaue, stelle ich fest, dass
fast alle imperativ programmieren wie ich. So richtig durchgesetzt hast du dich
noch nicht, oder?
F: Viele kennen mich halt nicht - das ist irgendwie schade. Die, die mich
kennen, benutzen meinen deklarativen Ansatz beim Entwickeln von Prototypen.
Es geht hier darum, schnell ein System zu entwickeln, das korrekt arbeitet bzw.
das gewünschte Verhalten zeigt. Es kommt nicht darauf an, das
Laufzeitverhalten zu optimieren oder Benutzerfreundlichkeit zu gewährleisten.
I: Kannst du das an einem Beispiel klarmachen?
F: Gerne, aber erst im nächsten Abschnitt.
FUNKTION summe:
{Fall 1:}
n == 0: summe(n)->0
{Fall 2:}
n > 0: summe(n)->n+summe(n-1)
58
Beschreiben statt Befehlen
Imperative Programmierung besteht darin, eine (mehr oder weniger abstrakte) Maschine mit
Hilfe von Anweisungen zu steuern.
Dabei wird beschrieben, wie die Ergebnisse berechnet werden sollen. Zentrale Bausteine
imperativer Programme sind Wertzuweisungen, die i.a. den momentanen Variablenzustand
(Speicherzustand) verändern. Imperative Programmierung ist wegen der Möglichkeit,
Seiteneffekte zu produzieren, recht fehleranfällig.
Deklarative Programmierung besteht darin, den Problemkontext (die Miniwelt) mit gegebenen
Mitteln zu beschreiben. Funktionale Programmierung - als eine Form der deklarativen
Programmierung - benutzt hierzu Funktionen.
Bei der deklarativen Programmierung wird somit beschrieben, was in der Modellwelt gelten
soll.
Die funktionale Programmierung arbeitet dabei ohne Speichervariablen. Variablen kommen hier
nur als Funktionsvariablen zur Übergabe von Funktionsargumenten vor. Seiteneffekte sind
demnach in der funktionalen Programmierung nicht möglich. Das Verhalten einer Funktion wird
vollständig durch die Funktionsdeklarationen festgelegt.
Funktionale Programmierung erfolgt auf einem höheren Abstraktionsniveau. Es ergeben sich
dadurch Programme, die kürzer, leichter zu durchschauen und weniger fehleranfälliger sind.
Funktionale Programmierung eignet sich zum „Prototyping“.
59
Prototyping
Bei der Entwicklung neuer Produkte wird zunächst oft ein Prototyp erstellt. Dabei handelt es
sich um ein funktionsfähiges Modell, das dem geplanten Produkt in vielen Bereichen
entspricht, meist aber auch eine Reihe von Vereinfachungen aufweist.
Prototypen werden - insbesondere bei komplexen
technischen Systemen - zunächst einmal erstellt, um
Erfahrungen mit dem neuen System und seiner
Entwicklung zu sammeln.
Prototypen kommen entsprechend auch bei der
Entwicklung komplexer Software zum Einsatz. Wir
skizzieren im Folgenden, wie ein Prototyp zu einem
RSA-Kryptosystem mit Hilfe der funktionalen
Programmierung erstellt werden kann.
60
Prototyp zu einem Kryptosystem
Beim RSA-Verfahren werden natürliche Zahlen mit Hilfe eines (öffentlichen bzw. privaten)
Schlüssels in neue Zahlen umwandelt.
from kryptosystem_rsa import *
# Testprogramm
n = 116063
e = 4097
d = 51137
oeffentlicherSchluessel = (e, n)
privaterSchluessel = (d, n)
s0 = 'Köln'
print(s0)
s1 = verschluesseln(s0, oeffentlicherSchluessel, codierenZeichenketteZahlenliste)
print(s1)
s2 = entschluesseln(s1, privaterSchluessel, decodierenZahlenlisteZeichenkette)
print(s2)
>>>
Köln
[99070, 100824, 50230]
Köln
Die Funktionen zur Realisierung des Prototyps findet man auf den Seiten von www.infschule.de (1.22.4.2).
61
Literaturhinweise
[Becker 99] K. Becker: Funktionale Programmierung. Materialien zum Lehrplan Informatik.
LMZ 1999. (http://informatikag.bildung-rp.de/html/funktprog.html)
[Becker 00] K. Becker: Problemlösen mit dem Computeralgebrasystem Derive - informatisch
betrachtet. (http://informatikag.bildung-rp.de/html/derive.html)
[Becker 04] K. Becker: Funktionale Programmierung mit Caml. (http://informatik.bildungrp.de/fileadmin/user_upload/informatik.bildung-rp.de/Weiterbildung/pps/WBFunktionaleProgrammierungCaml.pps)
[Fischbacher 97] T. Fischbacher: Funktionale Programmierung. In: LOG IN 17 (1997) Heft 3 /
4, S. 24-26.
[ISB 97] Staatliches Institut für Schulpädagogik und Bildungsforschung München (Hrsg.):
Funktionales Programmieren in Gofer. Baustein zur Didaktik der Informatik. München, 1997.
[Puhlmann 98] H. Puhlmann: Funktionales Programmieren - Eine organische Verbindung von
Informatikunterricht und Mathematik. In: LOG IN 18 (1998) Heft 2, S. 46-50.
[Schwill 93] A. Schwill: Funktionale Programmierung mit Caml. In: LOG IN 13 (1993) Heft 4,
S. 20-30.
[Wagenknecht 94] Christian Wagenknecht: Rekursion. Ein didaktischer Zugang mit
Funktionen. Bonn: Dümmlers Verlag 1994.
[Wolff von Gudenberg 96] J. Wolff. von Gudenberg: Algorithmen, Datenstrukturen, Funktionale
Programmierung. Eine praktische Einführung mit Caml Light. Bonn: Addison-Wesley 1996.
Herunterladen