Funktionale Programmierung 1 Funktionale Programmierung: Vorlesungsüberblick 1. Funktionale Programmierung Prinzipien funktionaler Programmierung Funktionale Programmierung in prozeduralen Sprachen Rekursive Programmierung Aufwandsbetrachtungen rekursiver Programme 2. Semantik funktionaler Programme Induktiver Beweis Induktive Deutung Fixpunktdeutung 3. Einführung in die rein funktionale Programmiersprache Scheme 4. Verfahren zur automatischen Speicherverwaltung 2 Eigenschaften rein funktionaler Programmierung Alle Programme und Prozeduren sind Funktionen und unterscheiden deutlich zwischen hereinkommenden Werten (Parameter) und herausgehenden Werten (Ergebnisse) Programmausführung in funktionalen Programmen ist die Auswertung eines Ausdrucks Es gibt keine Variablen Variablen werden durch Parameter ersetzt Es gibt keine Zuweisungen und keine Schleifen Schleifen werden durch rekursive Aufrufe ersetzt Der Wert einer Funktion hängt nur vom Wert der Parameter ab und nicht von der Reihenfolge der Berechnungen 3
Funktionale und imperative Programmierung Vorteile funktionaler Programmierung gegenüber imperativer Programmierung einheitliche Betrachtung von Programmen als Funktionen Behandlung von Funktionen als Daten (=Werte) Einschränkung von Seiteneffekten Automatische Speicherfreigabe Flexibilität, Prägnanz in der Notation, einfache Semantik Hauptnachteil funktionaler Programmierung ist die Ineffizienz der Programmausführung Anwendungsgebiete: künstliche Intelligenz, mathematische Beweissysteme, Logikanwendungen 4 Funktionales Programmieren in imperativen Sprachen Imperative Programmiersprachen (wie Modula-3) besitzen im allgemeinen ebenfalls mehr oder weniger ausgereifte Formen der funktionalen Programmierung rein funktionale Programmierung in imperativen Sprachen kann durch folgende Einschränkungen bei der Auswahl der gewählten Programmierelemente erreicht werden ausschließliche Verwendung von Funktionen zur Beschreibung des Kontrollflusses eines Programms wird alleinig auf Selektion und Funktionsaufruf zurückgegriffen keine Verwendung von Variablen, Zuweisungen und Schleifen in den Programmen Mächtigkeit des so eingeschränkten Programmiermodells entspricht dem der uneingeschränkten imperativen Sprache 5 Voraussetzungen (Euklidischer Algorithmus) Euklidischer Algorithmus kann zur Berechnung des größten gemeinsamen Teilers (ggt) benutzt werden Eine Zahl ggt heißt größter gemeinsamer Teiler zweier Zahlen a und b falls gilt: ggt teilt a und b für jeden gemeinsamen Teiler d von a und b gilt: d teilt die Zahl ggt wichtige Eigenschaften des ggt: ggt(x,0) = x ggt(x, y) = ggt(y, x mod y) für x,y > 0 6
Modula-3 Programm (Euklidischer Algorithmus) PROCEDURE ggt(a,b : INTEGER) : INTEGER= VAR tmp : INTEGER; LOOP IF b = 0 THEN RETURN a; tmp := a; a := b; b := tmp MOD b; 7 Modula-3-Programm (Rekursive Funktion) PROCEDURE ggt(a,b : INTEGER) : INTEGER= IF b = 0 THEN RETURN a; RETURN ggt(b, a MOD b) 8 Allgemeiner Entwurf rekursiver Funktionen analog zum Beweisen mit vollständiger Induktion stellt man sich die Frage: wie wird der Basisfall gelöst? der absolute Trivialfall der einfachste nicht-triviale Fall wie kann der allgemeine Fall der Größe n auf die Lösung für eine Größe n < n reduziert werden? aus der so erzeugten Spezifikation können bedingte Gleichungen abgeleitet werden, die dann in Programmcode umgesetzt werden derartig konstruierte rekursive Funktionen können dann mittels Induktion als korrekt bewiesen werden 9
Rekursive Funktionen: Mathematische Operationen PROCEDURE mod (n,m:integer):integer = (* n 0, m > 0*) IF n < m THEN RETURN n; RETURN mod(n-m,m); END mod; 10 Rekursive Funktionen: Mathematische Operationen PROCEDURE binom (n,k:integer):integer =(*n,k 0, n k*) IF n = k OR k = 0 THEN RETURN 1; RETURN binom(n-1,k-1) + binom(n-1,k); END binom; 11 Rekursive Funktion: Erkennen eines Palindroms PROCEDURE h (n : INTEGER) : INTEGER = (* n 0 *) IF n < 10 THEN RETURN 1 RETURN 10 * h(n / 10) END h; PROCEDURE spiegel (n : INTEGER) : INTEGER = (* n 0 *) IF n < 10 THEN RETURN n RETURN mod(n,10) * h(n) + spiegel(n/10) END spiegel; PROCEDURE palindrom (n: INTEGER) : BOOLEAN = (*n 0*) RETURN spiegel(n) = n END palindrom; 12
Rekursionsformen: Lineare Rekursion in einer rekursiven Funktionsdeklaration für f tritt ein Aufruf von f in jedem Zweig einer Fallunterscheidung höchstens einmal auf PROCEDURE sum(i, j : INTEGER) : INTEGER = IF i > j THEN RETURN 0; RETURN i + sum(i + 1, j); END sum; 13 Rekursionsformen: Repetitive Rekursion eine linear rekursive Funktion f heißt repetitiv rekursiv, falls alle rekursiven Aufrufe in den Zweigen einer Fallunterscheidung als äußerste (letzte) Aktion vorkommen PROCEDURE ggt(u,v : INTEGER) : INTEGER = IF v = 0 THEN RETURN u; RETURN ggt(v, u MOD v); 14 Rekursionsformen: Kaskardenartige Rekursion treten in mindestens einem Zweig einer Fallunterscheidung zwei oder mehr rekursive Aufrufe auf, so spricht man von einer kaskaden- oder baumartigen, nichtlinearen Rekursion PROCEDURE binom (n,k:integer):integer = IF n = k OR k = 0 THEN RETURN 1; RETURN binom(n-1,k-1) + binom(n-1,k); END binom; 15
Rekursionsformen: Vernestete Rekursion treten im Rumpf einer rekursiven Funktion f in den aktuellen Parameterausdrücken eines rekursiven Aufrufs von f weitere rekursive Aufrufe von f auf, so heißt f vernestet. PROCEDURE ackermann (m,n:integer):integer = (*m,n 0*) IF m = 0 THEN RETURN n + 1; IF n = 0 THEN RETURN ackermann(m-1,1) RETURN ackermann(m-1, ackermann(m,n-1)); END ackermann; 16 Effizienz funktionaler Programme Bewertungskriterien für den Auswertungsaufwand eines funktionalen Programms Anzahl der auf algorithmischer Basis bei der Berechnung auszuführenden Grundoperationen Anzahl der durchgeführten rekursiven Funktionsaufrufe Aufruftiefe der rekursiven Funktionen Effizienz funktionaler Programme leidet oft besonders unter dem sehr kostspieligen Aufbau der Funktionsrahmen während der rekursiven Berechnung der Funktionsergebnisse zur Effizienzverbesserung werden funktionale Programme vom Übersetzer oft vor der Ausführung wieder in eine nicht rekursive Form gebracht 17 Laufzeitstapel beim rekursiven Aufruf Basis Stapel Wachstum Rahmen der aufrufenden Funktion Rahmen der aufgerufenen Funktion top of stack 18
Bestandteile von Funktionsrahmen Parameter Temporär genutzter Platz Rücksprungadresse Statischer Verweis Dynamischer Verweis 19 Automatische Transformation rekursiver Aufrufe Anlegen der Funktionsrahmen zur Ausführung einer rekursiven Funktion ist sowohl sehr speicher- als auch sehr zeitintensiv Übersetzer für funktionale Sprachen versuchen daher bei der Programmerzeugung intern rekursive Funktionen wieder in Schleifen zu transformieren repetitiv rekursive Funktionen eignen sich besonders zur einfachen und schnellen Rücktransformation in Schleifen nicht repetitiv rekursive Funktionen können mittels akkumulierender Parameter in repetitive Funktionen transformiert werden 20 Transformation von rekursiven Aufrufen in Schleifen Eigenschaften repetitiv rekursiver Funktionen: vor Auswertung eines neuen rekursiven Aufrufs ist die Auswertung des vorhergehenden rekursiven Aufrufs völlig abgeschlossen aktuelle Parameterwerte müssen nicht aufbewahrt werden Umwandlung repetitiv rekursiver Funktionen in Schleifen: für jeden Funktionsparameter p i wird eine Variable t i angelegt Funktionsrumpf wird in eine Endlosschleife umgewandelt nicht rekursive Fallunterscheidungen bleiben unverändert rekursive Fallunterscheidungen werden in 2 Schritten modelliert: Zuweisungen der aktuellen Parameterwerte a i des rekursiven Aufrufs zu den temporären Variablen t i Zuweisung der den t i zugewiesenen Werte an die formalen Parametern der Funktion p i 21
Beispiel: Repetitiv rekursive Funktion PROCEDURE ggt(u,v : INTEGER) : INTEGER = IF v = 0 THEN RETURN u; RETURN ggt(v, u MOD v); 22 Beispiel: Transformation in eine Schleife PROCEDURE ggt(u,v : INTEGER) : INTEGER = (* Vom Übersetzer eingefügte Hilfsvariablen *) VAR t1, t2 : INTEGER; LOOP IF v = 0 THEN RETURN u; (* Auswertung der aktuellen Parameter *) t1 := v; t2 := u MOD v; (* Neuzuweisung der Parameter *) u := t1; v := t2; (* IF *) (* Schleife *) 23 Transformation nicht repetitiv rekursiver Funktionen prinzipiell kann jede rekursive Funktion in die repetitive Form gebracht und anschließend in eine Schleife transformiert werden einfach gestaltet sich die Transformation von nur linearen Rekursionen in das repetitiv rekursive Gegenstück in nur linearen Rekursionsformen ist in jeder Fallunterscheidung höchsten ein rekursiver Aufruf erlaubt rekursiver Aufruf in nur linearen Funktionen muss jedoch nicht die letzte Operation einer Fallunterscheidung sein Transformation von nur linearer in repetitiv lineare Rekursion rekursive Funktionsdeklaration wird um einen zusätzlichen Ergebnisparameter erweitert Operationen, die sonst erst nach Beendigung des rekursiven Aufrufes durchgeführt würden, werden vorab berechnet und dem Ergebnisparameter beim rekursiven Aufruf übergeben 24
Beispiel: Nicht repetitiv rekursive Funktion PROCEDURE sum(i, j : INTEGER) : INTEGER = IF i > j THEN RETURN 0; RETURN i + sum(i + 1, j); END sum; 25 Beispiel: Transformation in repetitiv rekursive Form PROCEDURE sum1(i, j, sumsofar : INTEGER) : INTEGER = IF i > j THEN RETURN sumsofar; RETURN sum1(i + 1, j, sumsofar + i) END sum1; PROCEDURE sum(i, j : INTEGER) : INTEGER = RETURN sum1(i, j, 0); END sum; 26