Konzepte der Programmiersprachen Funktionale Programmierung Lehrstuhl Prof. Plödereder Eduard Wiebe Institut für Softwaretechnologie Abteilung Programmiersprachen und Übersetzerbau Sommersemester 2007 Funktionale Programmierung Grundidee: I Beschreibung des Programms durch Denition und Anwendung von Funktionen (und Funktionalen) I Abwendung vom zustandsorientierten, imperativen (d. h. zuweisungsorientierten) (von-Neumann-)Modell I Ausführungsmodell ist im Prinzip der λ-Kalkül Im Vergleich imperatives (von-Neumann-)Modell: I Sequenz von Ausführungsschritten (statements) I Variable (Speicherplatz) mit änderbaren Werten I Zuweisung I Iteration Funktionales Modell I Verknüpfung von Funktionen I Funktionsparameter und evtl. Benennung berechneter Werte I keine Zuweisung! I Rekursion I Funktionen als Parameter und Resultat von Funktionen Funktionen, λ-Kalkül Funktion ist eine Abbildung von Denitionsbereich (domain) in Wertebereich (range) Beispiel plus : Z ×Z →Z deniert durch: ( , )≡x +y plus x y Die Denition der Funktion x + y wird dabei an den Namen gebunden. x und y sind Funktionsparameter. In λ-Kalkül-Schreibweise (Präxnotation): λ x, y. + x y plus Funktionen, λ-Kalkül (Fortsetzung) Die im λ-Kalkül dargestellte Funktion ist anonym: Ihr wird kein Name zugewiesen. Der Punkt ('.') trennt die Parameterliste von der Funktionsbeschreibung. x und y sind gebundene Variablen. Auswertung im λ-Kalkül: (λ x , y . + x y )(2, 3) ≡ +2 3 ≡ 5 Bei der Auswertung werden die Werte (Argumente) textuell für die Parameter eingesetzt (Substitution). currying: Rückführung von λ-Ausdrücken mit mehreren gebundenen Variablen auf λ-Ausdrücke mit genau einer gebundenen Variablen (nach dem Mathematiker H. B. Curry). Funktionen als Werte Beispiel plus = λx .λy . + x y , dann heiÿt x freie Variable für λy -Ausdruck und der Wert des Ausdruckes λx ist eine Funktion. Auswertung: (λx .λy . + x y )(2)(3) ≡ (λy . + 2 y )(3) ≡ +2 3 Achtung: vor Substitution evtl. konsistente Ersetzung der inneren gebundenen Variablen: (λ x .λ y . + x y )(y )(3) 6= (λ y . + y y )(3) Eigenschaften der funktionalen Programmierung Vorteile: I Funktionen sind Funktionen im mathematischen Sinne I keine Seiteneekte (referential transparency) I Jede Funktionsanwendung bezeichnet unveränderlich einen eindeutigen Wert, unabhängig vom Aufruf-Kontext. Folglich: I I I I Kenntnis des globalen Programmzustands für das Verstehen der Funktion nicht erforderlich. Anwendung ergibt auf gleichen Parametern gleiches Resultat können (nicht verschachtelte) Funktionsanwendungen parallel ausgeführt werden sind Funktionen beliebig komponierbar (sofern freie Variablen fest gebunden sind) I Programmbeschreibung näher an einer formalen Spezikation der gewünschten Berechnung und somit leichter verizierbar I Programmtransformationen sind relativ einfach durchführbar Eigenschaften der funktionalen . . . (Fortsetzung) Nachteile: I Eziente Implementierung auf herkömmlichen Rechnerarchitekturen schwieriger als bei prozeduralen Sprachen I für Echtzeit- und maschinennahe Anwendungen daher kaum geeignet I Ausführungsmodell nur für mathematische Probleme naheliegend Funktionale Programmiersprachen I LISP ( 60) I I ∗ Common LISP∗ (statische Namensbindung) Scheme( 78)∗ I APL I FP (viele Konstruktorformen) I ML ( 77)∗ (Typprüfungen, Typinferenzsystem und überladene Funktionsdenitionen I Miranda (Turner 1985) I Hope (Burstall 1980) I Haskell (Hudak u. w. 90) sind nicht reine funktionale Sprachen, sondern enthalten auch imperative Elemente Grundelemente funktionaler Sprachen I Strukturen für Daten-Objekte (meist sehr eingeschränkt) I Einige primitive Funktionen: arithmetische/logische Operationen und Operationen zur Manipulation der Datenstrukturen I Funktionale (functional forms) zur Schaung/Kombination neuer Funktionen aus existierenden Funktionen → spezielle Form höherwertiger Funktionen (higher order functions): nehmen Funktion(en) als Parameter und/oder liefern Funktion als Resultat I Die Anwendungs-Operation Programmierung in funktionalen Sprachen: I Denition neuer Funktionen mittels Funktionalen I Möglichkeit, Funktionen zu benennen, d. h. an Namen zu binden (zur Wiederverwendung bereits denierter Funktionen) LISP John McCarthy (1960): ursprünglich rein funktionale Sprache Grundideen: I Datenobjekte sind entweder I Atome (Zahlen, Strings, Namen, Funktionen . . . ) oder I Listen, deren Elemente entweder Listen oder Atome sind I Für Atome und Listen stehen primitive Funktionen zur Verfügung I Funktionen sind Datenobjekte! (als Liste deniert) I Die primitiven Operationen auf Funktionen erlauben die Konstruktion neuer Funktionen (durch Komposition) und die Auswertung von Funktionen. Mit sehr wenigen Konstrukten kann eine sehr ausdrucksstarke Programmiersprache konstruiert werden. LISP Ausführungsmodell Funktion EVAL wird auf eine Liste angewandt und hat die folgende Semantik: (EVAL '(A B C)) ≡ wende (EVAL A) auf Parameter (EVAL B) und (EVAL C) an (EVAL A) ≡ Wert von A (falls A Name ist) Primitive Listenoperationen in LISP I I I I I (QUOTE x) ergibt als Wert 'x'. Abkürzung: 'x (CAR '(A B C)) ergibt A (1. Element der Liste) (CDR '(A B C)) ergibt (B C) (Liste minus 1. Element) wegen der Häugkeit dieser Operationen auch (CAAAR x) ≡ (CAR (CAR (CAR x))) (CAD..ADR x) ≡ (CAR (CDR ..(CAR (CDR x))..)) (CONS 'A '(B C)) ergibt (A B C) (CONS '(A B) '(B C)) ergibt ((A B) C D) (NULL x) ergibt 'T', wenn (EVAL x) '() ergibt '() sonst '() ist benannt NIL, d. h. (EVAL NIL) ergibt '() Lisp-Funktionswerte I haben die Form (LAMBDA Parameterliste Definition) I können (wie alle anderen Werte) benannt werden: I (DEFINE '(CDAR (LAMBDA (list) (CAR (CDR list))))) Anwendung: (CDAR '(A B)) ≡ apply (LAMBDA ...) '(A B) ≡ (CAR (CDR '(A B))) ≡ (CAR '(B)) ≡ 'B COND (COND ( ( bed1 ausdruck1 )) ( ( bed2 ausdruck2 )) : ( ( bedn ausdruckn ) ) ) Semantik: EVAL (COND . . . ) ergibt if ( EVAL bed1 ) = T dann ( EVAL ausdruck1 ) e l s i f ( EVAL bed2 ) = T dann ( EVAL ausdruck2 ) dann ( EVAL ausdruckn ) : e l s i f else ( EVAL bedn ) = T '() Beispiel ( DEFINE '( fac (LAMBDA (COND (n) ( ( EQ (T (fac 4) ergibt 24 ( ∗ n 0) n 1) ( fac (+ n 1)))))))) LISP-Dialekte I Hinzufügung weiterer Funktionale (auÿer Komposition), z. B. MAPCAR (entspricht α in FP) I Hinzufügung imperativer, nicht-applikativer Elemente wie I Zuweisung (SET), I Sequenz (PROG) I und Manipulation von (bestehenden) Datenstrukturen (RPLACA = replace car) aus Ezienzgründen I statische statt dynamische Namensbindung (Common Lisp) FP John Backus (Anfang 70er): rein funktionale Sprache I Datenstrukturen bestehen aus Atomen (Zeichenfolge) bzw. Folgen von Atomen I viele Basisfunktionen (z. B. tail, equals, reverse, length, usw.) I Anwendungsoperator ':' I xe Menge von Funktionalen zur Erzeugung neuer Funktionen aus bestehenden basis- bzw. benutzerdenierten Funktionen, d. h. (basis- und benutzerdenierte) Funktionen können keine Funktionen als Parameter erhalten bzw. als Resultat liefern. Gründe: Unbeschränkte Freiheit, Funktionen kombinieren zu können, führt zu einer unübersichtlichen Programmiersprache und unübersichtlichen Programmen. FP (Fortsetzung) I Sorgfältige Wahl der Funktionale ermöglicht einfache Transformationen und Korrektheitsbeweise von FP-Programmen. Transformationsregeln können ebenfalls durch FP-Syntax beschrieben werden (d. h. keine Metasprache erforderlich!). Beispiele (Funktionale in FP (Auswahl)) 1. Komposition: (f ◦ g ) : x ≡ f : (g : x ) 2. Konstruktion: [f1 , f2 , . . . , fn ] : x ≡ (f1 : x , . . . , fn : x ) 3. All-Anwendung: α f : (x1 , . . . , xn ) ≡ (f : x1 , . . . , f : xn ) Funktionale (Fortsetzung) Beispiele 4 Bedingte Form: (IFp f g ) : n ≡ if p : n 5 While-Iteration: (WHILE p f ) : x ≡ if p : = T then f : n else g : n then (WHILE p f ) : (f : x ) else x 6 Einfügen (vereinfacht, für n > 2) /f : (x1 , . . . , xn ) ≡ f : (x1 , /f : (x2 , . . . , xn )) 7 Transposition: trans : ((x11 , . . . , x1n ), . . . , (xn1 , . . . , xnn )) ≡ ((x11 , . . . , xn1 ), . . . , (x1n , . . . , xnn )) x Funktionale (Fortsetzung) Beispiel (Skalarprodukt) DEF IP ≡ (/+) ◦ (α∗) ◦ trans Anwendung auf eine Liste ((1, 2, 3), (4, 5, 6)) (/+) ◦ (α∗) ◦ trans : ((1, 2, 3), (4, 5, 6)) = (/+) ◦ (α∗) : ((1, 4), (2, 5), (3, 6)) = (/+) : (4, 10, 18) = + : (4, + : (10, + : (18))) = + : (4, + : (10, 18)) = + : (4, 28) = 32 Delayed Evaluation, Lazy Evaluation I I Am Beispiel COND in LISP sieht man, dass nicht zwangsläug alle Parameter ausgewertet werden müssen. Beobachtung zum Prinzip erheben: Parameter müssen erst ausgewertet werden, wenn ihr Wert benötigt wird → lazy evaluation Vorteile: I vermeidet überüssige Auswertung in vielen Fällen I ermöglicht Arbeiten mit unendlichen Datenstrukturen (Miranda, Haskell) Realisierung: Statt Wert-Eintrag im Datenobjekt, Eintrag der Funktion, die jeweils im Bedarfsfall den (nächsten) Wert berechnet. I gewährleistet Terminierung des Programms, falls nicht benötigte Parameter (-Ausdrücke) nicht terminieren würden I in Scheme über delay und force kontrollierbar