Funktionale Programmierung mit Caml Klaus Becker 2004 Programmieren mit Funktionen 2 Funktionale Programmierung 0 KB 0 akzeptor 1 g u Ok! 1 00011011 Funktionale Programmierung 3 KB Teil 1 Funktionen als Programme 4 Die Welt der Automaten Funktionale Programmierung 0 KB 0 1 g u 1 Mit Hilfe endlicher Automaten kann man formale Sprachen erkennen. Der dargestellte endliche Automat erkennt die Sprache der 0-1-Worte mit gerader Parität (gerader Anzahl von 1en). 5 Eine Automatenbeschreibung Funktionale Programmierung 0 KB 0 1 g u 1 Zustandsmenge: Z = {g, u} Anfangszustand: za = g Endzustände: zE = {g} Eingabemenge: E = {0, 1} Überführungsfunktion: : (g, 0) g : (u, 0) u : (g, 1) u : (u, 1) g Funktionale Modellierung Funktionale Programmierung 6 KB Zustandsmenge: Z = {g, u} Eingabemenge: E = {0, 1} Anfangszustand: za = g Endzustände: zE = {g} Überführungsfunktion: : : : : (g, (g, (u, (u, 0) 1) 0) 1) type zustandP = g | u;; type eingabeP = e0 | e1;; let anfangszustandP = g;; let endzustandP = function g -> true | u -> false;; let deltaP = function (g, e0) -> g | (g, e1) -> u | (u, e0) -> u | (u, e1) -> g ;; g u u g Funktionale Modellierung 7 Funktionale Programmierung anfangszustandP KB endzustandP: g true g u g u zustand zustand true false bool type zustandP = g | u;; type eingabeP = e0 | e1;; let anfangszustandP = g;; let endzustandP = function g -> true | u -> false;; let deltaP = function (g, e0) -> g | (g, e1) -> u | (u, e0) -> u | (u, e1) -> g ;; deltaP: (g, e1) u g u zustand e0 e1 eingabe 8 Modellierungskonzept type zustandP = g | u;; Funktionale Programmierung type eingabeP = e0 | e1;; KB let anfangszustandP = g;; let endzustandP = function g -> true | u -> false;; Typdeklaration Konstantendeklaration Funktionsdeklaration let deltaP = function (g, e0) -> g | (g, e1) -> u | (u, e0) -> u | (u, e1) -> g ;; Objektbereiche werden mit Hilfe von Typen beschrieben. Eigenschaften von Objekten und Zusammenhänge zwischen Objekten mit Hilfe von Funktionen (und Konstanten). 9 Auswertung von Deklarationen #Type zustandP defined. Funktionale Programmierung #Type eingabeP defined. KB #anfangszustandP : zustandP = g #endzustandP : zustandP -> bool = <fun> #deltaP : zustandP * eingabeP -> zustandP = <fun> Auswertung durch Caml type zustandP = g | u;; type eingabeP = e0 | e1;; let anfangszustandP = g;; let endzustandP = function g -> true | u -> false;; let deltaP = function (g, e0) -> g | (g, e1) -> u | (u, e0) -> u | (u, e1) -> g ;; Benutzereingabe Signaturanalyse 10 Signatur der Funktion Funktionale Programmierung Auswertung: KB #deltaP : zustandP * eingabeP -> zustandP = <fun> Die Funktion deltaP ordnet einem Paar bestehend aus einem Objekt vom Typ zustandP und einem Objekt vom Typ eingabeP ein Objekt vom Typ zustandP zu. Benutzereingabe: type zustandP = g | u;; type eingabeP = e0 | e1;; ... let deltaP = function (g, e0) -> g | (g, e1) -> u | (u, e0) -> u | (u, e1) -> g ;; Funktionsdeklaration Funktionsanwendung 11 Auswertung: Funktionale Programmierung ... #deltaP : zustandP * eingabeP -> zustandP = <fun> #deltaP(u, e0);; - : zustandP = u Die Auswertung des funktionalen Ausdrucks delta(e0, g) liefert das Objekt u vom Typ zustand. Aktuelle Benutzereingabe: Vorherige Benutzereingaben: deltaP(u, e0);; type zustandP = g | u;; type eingabeP = e0 | e1;; ... let deltaP = function (g, e0) -> g | (g, e1) -> u | (u, e0) -> u | (u, e1) -> g ;; KB Funktionsanwendung 12 Auswertung: Funktionale Programmierung #endzustandP(deltaP(deltaP(anfangszustandP, e1), e1));; KB - : bool = true Benutzereingabe: endzustandP(deltaP(deltaP(anfangszustandP, e1), e1));; Die Auswertung des funktionalen Ausdrucks deltaP(deltaP(anfangszustandP, e1), e1) liefert das Objekt u vom Typ zustandP. Funktionale Programme Funktionale Programmierung 13 Ein funktionales Programm besteht aus einer Menge von Funktionsdeklarationen (und evtl. benötigten Typdeklarationen) und einem funktionalen Ausdruck (Berechnungsausdruck). Funktionaler Ausdruck Funktionsdeklarationen Benutzereingabe: Benutzereingabe: deltaP(u, e0);; type zustandP = g | u;; type eingabeP = e0 | e1;; ... let deltaP = function (g, e0) -> g | (g, e1) -> u | (u, e0) -> u | (u, e1) -> g ;; KB 14 Programmierkonzept Funktionale Programmierung type zustandP = g | u;; KB type eingabeP = e0 | e1;; ... let deltaP = function (g, e0) -> g | (g, e1) -> u | (u, e0) -> u | (u, e1) -> g ;; deltaP(u, e0);; Mit Funktionen kann man programmieren. Die Programme bestehen aus Funktionsdeklarationen und einem funktionalen Berechnungsausdruck. Übung Funktionale Programmierung 15 Entwickeln Sie ein funktionales Programm zur Simulation einer Ampelschaltung mit Tag-Nacht-Betrieb. Testen Sie das Programm. KB Lösungsvorschlag 16 type zustandA = ro | rg | ge | gr | aa;; Funktionale Programmierung type eingabeA = t | n;; KB type ausgabeA = Ooo | OOo | ooO | oOo | ooo;; let anfangszustandA = ge;; let deltaA = (ro, t) -> (rg, t) -> (gr, t) -> (ge, t) -> (aa, t) -> (ro, n) -> (rg, n) -> (gr, n) -> (ge, n) -> (aa, n) -> function rg | gr | ge | ro | ge | aa | aa | aa | aa | ge ;; let lambdaA = function (ro, t) -> OOo | (rg, t) -> ooO | (gr, t) -> oOo | ... Funktionale Programmierung 17 KB Teil 2 Kontrollstrukturen Zielsetzung 18 Funktionale Programmierung 0 KB 0 1 00011011 Ok! g u 1 Es soll ein Akzeptor-System entwickelt werden, mit dem eine Folge von Eingaben verarbeiten werden kann und rückgemeldet wird, ob diese Folge in einen Endzustand überführt. Listen 19 Funktionale Programmierung 0 KB 0 1 [0; 0; 0; 1; 1; 0; 1; 1] g true u 1 Eine Liste in Caml besteht aus einer beliebigen, aber endlichen Anzahl von Elementen, die alle den gleichen Typ haben. Listenkonstruktoren 20 Leere Liste: Funktionale Programmierung Die leere Liste wird mit [] dargestellt. KB Hinzufügen eines Elementes: Mit Hilfe des Hinzufügoperators :: kann ein Element vorne in eine Liste eingefügt werden. Bsp.: 1 :: [2; 3] [1; 2; 3] 21 Spezifikation des Automatensimulators Funktionale Programmierung Spezifikation: simulatorP Zustand Zustand nach der Verarbeitung der Eingaben Eingabeliste Bsp.: simulatorP(g, [e0; e1; e1; e1]) u Spezifikation: akzeptorP Eingabeliste Bsp.: akzeptorP([e0; e1; e1; e1]) false KB true / false 22 Rekursive Problemreduktion Fall 1: Verarbeite eine leere Eingabenliste Funktionale Programmierung simulartorP(g, []) g Fall 2: Verarbeite eine nicht-leere Eingabenliste simulatorP(g, [e1; e0; e1]) simulatorP(u, [e0; e1])] g g Rekursive Problemreduktion: Reduktion des Problems auf ein entsprechendes, aber „verkleinertes“ Problem KB Reduktionsregeln 23 Fall 1: Verarbeite eine leere Eingabenliste Funktionale Programmierung simulartorP(g, []) g KB Fall 2: Verarbeite eine nicht-leere Eingabenliste simulatorP(g, [e1; e0; e1]) simulatorP(u, [e0; e1])] g g let rec simulatorP = function (z, []) -> z | (z, e::r) -> simulatorP(deltaP(e,z), r);; Reduktionsketten Funktionale Programmierung 24 let rec simulatorP = function (z, []) -> z | (z, e::r) -> simulatorP(deltaP(e,z), r);; simulatorP(g, [e1; e0; e1]) simulatorP(deltaP(g, e1), [e0; e1]) simulatorP(delta(deltaP(g, e1), e0), [e1]) simulatorP(delta(delta(deltaP(g, e1), e0), e1), []) delta(delta(deltaP(g, e1), e0), e1) delta(delta(u, e0), e1) delta(u, e1) g Caml berechnet mit Hilfe solcher Reduktionsketten den Wert des Ausgangsterms. KB 25 Paritäts-Akzeptor Funktionale Programmierung #simulatorP : zustandP * eingabeP list -> zustandP = <fun> KB #akzeptorP : eingabeP list -> bool = <fun> let rec simulatorP = function (z, []) -> z | (z, e::r) -> simulatorP(deltaP(z,e), r);; let akzeptorP = function w -> endzustandP(simulatorP(anfangszustandP,w));; 26 Test des Akzeptors #akzeptorP([e1;e0;e1]);; Funktionale Programmierung - : bool = true KB akzeptorP([e1;e0;e1]);; 27 Kontrollstrukturen Funktionale Programmierung Fallunterscheidung KB let rec simulatorP = function (z, []) -> z | (z, e::r) -> simulatorP(deltaP(e,z), r);; Rekursion let akzeptorP = function w -> endzustandP(simulatorP(anfangszustandP,w));; Funktionskomposition Funktionsdeklarationen werden mit Hilfe von - Funktionskomposition, - Fallunterscheidungen und - Rekursion aufgebaut. Übung 28 Funktionale Programmierung Spezifikation: KB simulatorA Zustand Eingabeliste Liste mit den erzeugten Ausgaben Bsp.: simulatorA(ro, [t; t; t; t]) [OOo; ooO; oOo; Ooo] Entwickeln Sie eine Funktionsdeklaration zur Implementierung eines Ampelsimulators. Lösungsvorschlag 29 #simulatorA : zustandA * eingabeA list -> ausgabeA list = <fun> Funktionale Programmierung #- : ausgabeA list = [Ooo; OOo; ooO; ooO; ooo; oOo] KB ... (* Deklaration der Ampel *) ... let rec simulatorA = function (z, []) -> [] | (z, e::r) -> lambdaA(z,e) :: simulatorA(deltaA(z,e), r);; simulatorA(anfangszustandA, [t;t;t;t;n;n]);; Funktionale Programmierung 30 KB Teil 3 Datenstrukturen Zielsetzung 31 Funktionale Programmierung let anfangszustandP = g;; KB let endzustandP = function g -> true | u -> false;; let deltaP = function (g, e0) -> g | (g, e1) -> u | (u, e0) -> u | (u, e1) -> g ;; Ok! 00011011 Es soll ein universelles Akzeptor-System entwickelt werden, mit dem eine Folge von Eingaben mit einem beliebig vorgegebenen Automaten verarbeiten werden kann. 32 Spezifikation des Automatensimulators Spezifikation: Funktionale Programmierung simulator Automatenbeschreibung Zustand nach der Verarbeitung der Eingaben Zustand Eingabeliste Bsp.: simulator((g, (g true, u false), ...), g, [e0; e1; e1; e1]) u Spezifikation: akzeptor Automatenbeschreibung Eingabeliste true / false Bsp.: akzeptor((g, (g true, u false), ...), [e0; e1; e1; e1]) false KB Automatenbeschreibung 33 Spezifikation: Funktionale Programmierung simulator KB Automatenbeschreibung Zustand Eingabeliste Zustand nach der Verarbeitung der Eingaben Bsp.: simulator((g, (g true, u false), ...), g, [e0; e1; e1; e1]) u Eine Automatenbeschreibung ist ein Tripel (az, ez, de) mit: - az ist eine Konstante eines Zustandstyps 'a („Anfangszustand“). - ez ist eine Funktion 'a 'b, die jedem Zustand aus 'a einen Wert aus 'b zuordnet („Endzustand“). - de ist eine Funktion 'a * 'c 'a, die jedem Zustand aus 'a und jeder Eingabe aus einem Eingabetyp 'c einen neuen Zustand aus 'a zuordnet („Delta“). 34 Reduktionsregeln Funktionale Programmierung #simulator : ('a * 'b * ('c * 'd -> 'd)) * 'd * 'c list -> 'd = <fun> KB #akzeptor : ('a * ('a -> 'b) * ('c * 'a -> 'a)) * 'c list -> 'b = <fun> let rec simulator = function ((az,ez,de), z, []) -> z | ((az,ez,de), z, e::r) -> simulator((az,ez,de), de(z,e), r);; let akzeptor = function ((az,ez,de), w) -> ez(simulator((az,ez,de), az, w));; Test 35 #akzeptor(automatP, wortP);; Funktionale Programmierung - : bool = false let rec simulator = function ... let akzeptor = function ... type zustandP = g | u;; type eingabeP = e0 | e1;; let anfangszustandP = g;; let endzustandP = function g -> true | u -> false;; let deltaP = function (g,e0)->g| (g,e1)->u| (u,e0)->u| (u,e1)->g;; let automatP = (anfangszustandP, endzustandP, deltaP);; let wortP = [e0; e1; e1; e1];; KB akzeptor(automatP, wortP);; Datenstrukturen Funktionale Programmierung 36 KB let rec simulator = function ((az,ez,de), z, []) -> z | ((az,ez,de), z, e::r) -> simulator(...);; Funktion Tupel Liste Objekte können mit Hilfe von Tupelbildung und Listen zu neuen Einheiten zusammengefasst werden. Funktionen können als Eingabeobjekte für weitere Funktionen benutzt werden. Übung 37 Spezifikation: Funktionale Programmierung simulator KB Automatenbeschreibung Zustand Eingabeliste Liste mit den erzeugten Ausgaben Bsp.: simulator((...), ro, [t; t; t; t]) [OOo; ooO; oOo; Ooo] Entwickeln Sie eine Funktionsdeklaration zur Implementierung eines Automatensimulators. Testen Sie den Simulator. 38 Lösungsvorschlag Funktionale Programmierung #simulator : ('a * ('b * 'c -> 'b) * ('b * 'c -> 'd)) * 'b * 'c list -> 'd list = <fun> #transduktor : ('a * ('a * 'b -> 'a) * ('a * 'b -> 'c)) * 'b list -> 'c list = <fun> #ampel : zustandA * (zustandA * eingabeA -> zustandA) * (zustandA * eingabeA -> ausgabeA) = ge, <fun>, <fun> #- : ausgabeA list = [Ooo; OOo; ooO; oOo; ooo; oOo] ... (* Deklaration der Ampel *) ... let rec simulator = function ((az, de, la), z, []) -> [] | ((az, de, la), z, e::r) -> la(z,e) :: simulator((az, de, la), de(z,e), r);; let transduktor = function ((az, de, la), w) -> simulator((az, de, la), az, w);; let ampel = (anfangszustandA, deltaA, lambdaA);; transduktor(ampel, [t;t;t;t;n;n]);; KB Funktionale Programmierung 39 KB Teil 4 Deklarative Programmierung Funktionale Programmierung 40 Programmierstile begin automat.anfangszustand; for i := 0 to eingaben.Count-1 do begin e := eingaben[i]; a := automat.ausgabe(e); automat.neuerZustand(e); ausgaben.Add(a); end; end; Programmierung mit Wertzuweisungen Programmierung mit let rec simulator = function Funktionsdeklarationen ((az, de, la), z, []) -> [] | ((az, de, la), z, e::r) -> la(z,e) :: simulator((az, de, la), de(z,e), r);; let transduktor = function ((az, de, la), w) -> simulator((az, de, la), az, w);; let ampel = (anfangszustandA, deltaA, lambdaA);; KB transduktor(ampel, [t;t;t;t;n;n]);; Funktionale Programmierung 41 KB Imperative Programmierung begin z := anfangszustand; for i := 0 to n-1 do begin e := eingaben[i]; a := ausgabe(e); z := neuerZustand(e); ausgaben[i] := a; end; end; Programmierung mit Wertzuweisungen Wertzuweisungen sind die zentralen elementaren Bausteine imperativer Programme. Die Wertzuweisungen verändern (i. a.) den momentanen Variablenzustand (Speicherzustand). Imperative Programmierung 42 Funktionale Programmierung Beschreiben, wie die Ergebnisse berechnet werden sollen. KB A.-Zustand Anweisungen E.-Zustand {eingaben: [t][t][t][t]} z := anfangszustand; for i := 0 to n-1 do begin e := eingaben[i]; a := ausgabe(e); z := neuerZustand(e); ausgaben[i] := a; end; Registermaschine {ausgaben: [Ooo][OOo][ooO][oOo]} Imperative Programmierung besteht darin, eine mehr oder weniger abstrakte Registermaschine (Maschine mit Speicherzellen) mit Hilfe von Anweisungen zu steuern. Vorsicht: Seiteneffekte 43 PROGRAM Demo; Funktionale Programmierung VAR d: boolean; w1, w2: integer; KB function f(n: int.): int.; BEGIN if d THEN f := 2*n ELSE f := n; d := not d; END; BEGIN d := true; w1 := f(1)+f(2); w2 := f(2)+f(1); END. {d: ; w1: ; w2: } d := true; {d: true; w1: w1 := f(1) ; w2: + 2 f(2) ; 2 {d: true; w1: 4; w2: w1 := f(2) } + } f(1) ; 4 1 {d: true; w1: 4; w2: 5} Seiteneffekt: Veränderung einer globalen Variablen Imperative Programmierung ist wegen der Möglichkeit, Seiteneffekte zu produzieren, recht fehleranfällig. Funktionale Programmierung 44 KB Funktionale Programmierung let rec simulator = function ((az, de, la), z, []) -> [] | ((az, de, la), z, e::r) -> la(z,e) :: simulator((az, de, la), de(z,e), r);; let transduktor = function ((az, de, la), w) -> simulator((az, de, la), az, w);; let ampel = (anfangszustandA, deltaA, lambdaA);; transduktor(ampel, [t;t;t;t;n;n]);; Programmierung mit Funkionsdeklarationen Die funktionale Programmierung arbeitet 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 45 Funktionale Programmierung Beschreiben, was (welche funkt. Zusammenhänge) gelten soll. KB A.-Term transduktor(ampel,[t;t;t]);; ... Deklarationen let rec simulator = function ((az,de,la),z,[]) ->[]| ((az,de,la),z,e::r)->... Reduktionsmaschine ... E.-Term #- : ausgabeA list = [Ooo; OOo; ooO] Funktionale Programmierung besteht darin, die strukturellen Zusammenhänge der Miniwelt mit Hilfe von Funktionen zu beschreiben. Funktionale Programmierung 46 KB Fazit Funktionale Programmierung erfolgt auf einem höheren Abstraktionsniveau: keine Anweisungen an eine Maschine, sondern Beschreibung funktionaler Zusammenhänge. Konsequenzen: - Funktionale Programme sind kurz. - Funktionale Programme sind leicht zu verstehen. - Funktionale Programmierung ist wenig fehleranfällig. - Funktionale Programmierung eignet sich zum „Prototyping“. Prototyp eines Automatensimulationsprogramms: let rec simulator = function ((az, de, la), z, []) -> [] | ((az, de, la), z, e::r) -> la(z,e) :: simulator((az, de, la), de(z,e), r);; let transduktor = function ((az, de, la), w) -> simulator((az, de, la), az, w);; 47 Literaturhinweise Funktionale Programmierung [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) [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. [MacLennan ??] B.J. MacLennan: Functional Programming: Addison-Wesley ??. [Wagenknecht 94] Christian Wagenknecht: Rekursion. Ein didaktischer Zugang mit Funktionen. Bonn: Dümmlers Verlag 1994. KB [Wolff von Gudenberg 96] J. Wolff. von Gudenberg: Algorithmen, Datenstrukturen, Funktionale Programmierung. Eine praktische Einführung mit Caml Light. Bonn: Addison-Wesley 1996.