3.7 Lambda-Ausdruck zur Funktionsdefinition Allgemeine Struktur der special form zur Funktionsdefinition: (lambda (<parameter-list>) expression) <parameter-list>: eine Liste von Namen par1 … parn , auch formale Parameter genannt; die Liste kann auch leer sein expression: Ausdruck, auch Rumpf (body) der Funktion genannt Die Auswertung eines solchen lambda-Ausdrucks durch den Scheme-Interpreter liefert eine Funktion mit entsprechender Parameterzahl, die genau wie die vordefinierten Funktionen benutzt werden kann © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 175 3.7 Lambda-Ausdruck zur Funktionsdefinition Beispiel: > (lambda (x) (* x 2)) (lambda (a1) ...) > ((lambda (x) (* x 2)) 5) 10 Allgemeine Form der Funktionsanwendung: ((lambda (<parameter-list>) <exp>) <argument-list>) <argument-list>: Liste exp1 … expn von Ausdrücken, die zu n Argumenten arg1 … argn ausgewertet werden müssen. Die Zuordnung zwischen diesen Werten und den formalen Parametern erfolgt von links nach rechts, d.h. durch Bindung von pari an argi. © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 176 3.7 Lambda-Ausdruck zur Funktionsdefinition Namensbindung: Formale Parameter werden im Rumpf der Funktion vor Bindungen gleicher Namen in der Umgebung geschützt Äußere Bindungen haben keinen Einfluß auf durch lambda als Parameter festgelegte Namen! > (define x 100) > (define y 200) Diese drei x haben miteinander nichts zu tun! > ((lambda (x) (+ x y)) 33) 233 > ((lambda (x) (+ x y)) 2) 202 © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 177 3.7 Lambda-Ausdruck zur Funktionsdefinition Benennung von Funktionen wie bei der Bindung von Werten an Namen durch define Beispiel: > ((lambda (x) (∗ x x)) 5 ) 25 Alternativ: > (define square (lambda (x) (∗ x x))) > (square 5) 25 Auswertung: (square 5) → ((lambda (x) (∗ x x) ) 5) → (∗ 5 5) → 25 © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 178 3.7 Lambda-Ausdruck zur Funktionsdefinition Scheinbar unterschiedliches Verhalten bei Benutzung von Namen aus der Umgebung: > (define b (+ a 15)) reference to an identifier before its definition: a > (define f (lambda (x) (+ a x)))) ;; Keine Fehlermeldung > (f 15) reference to an identifier before its definition: a > (define a 1) > (f 15) 16 © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 179 3.7 Lambda-Ausdruck zur Funktionsdefinition Die bereits bekannte Funktionsdefinition: (define (<function-name> <par-list>) <exp>) ist die Kurzform von (define <function-name> (lambda (<par-list>) <exp>)) Beispiel: (define square (lambda (x) (∗ x x))) ist äquivalent zu: (define (square x) (∗ x x)) © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 180 3.8 Datentyp Funktionen Datentyp Funktion als Werkzeug zur Abstraktion: Scheme = Lisp + „first class“–Funktionen Funktionen werden in einer Programmiersprache als „Werte erster Klasse“ behandelt, wenn man sie • als Parameter an andere Funktionen übergeben, • zur Laufzeit erzeugen, • als Resultate von Funktionen zurückgeben kann Funktionen, die andere Funktionen als Parameter und/oder Resultat haben, heißen Funktionen höherer Ordnung. Scheme war eine der ersten Sprachen, die dieses Konzept konsequent umgesetzt hat. „First class“–Funktionen verbessern die Expressivität einer Programmiersprache, also ihre Fähigkeit, komplexe Sachverhalte möglichst klar und einfach auszudrücken © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 181 3.8.1 Funktionen als Parameter Zusammenfassung von Listen: Aus zwei ähnlichen Funktionen (define (sum l) (cond [(empty? (rest l)) (first l)] [else (+ (first l) (sum (rest l)))])) (define (mult l) (cond [(empty? (rest l)) (first l)] [else (* (first l) (mult (rest l)))]) definieren wir eine abstraktere Funktion (vgl. 3.6.3 Listenfunktionen): (define (list-op op l) (cond [(empty? (rest l)) (first l)] [else (op (first l) (list-op op (rest l)))])) © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 182 3.8.1 Funktionen als Parameter Beispiel: > (list-op + (list 1 2 3 4 5)) 15 > (list-op * (list 1 2 3 4 5)) 120 > (list-op min (list 1 2 3 4 5)) 1 > (list-op max (list 1 2 3 4 5)) 5 Quiz: Was berechnet der folgende Ausdruck? (list-op + (map sqr (list 1 2 3 4 5))) © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 183 3.8.1 Funktionen als Parameter Die Sortierfunktion sort (3.6.3 Listenfunktion) war auf die aufsteigende Sortierung von Zahlen beschränkt. Wenn man eine Vergleichsfunktion als Argument übergibt, wird sie erheblich vielseitiger einsetzbar. (define (insert x l) (cond [(empty? l) (list x)] [(<= x (car l)) (cons x l)] [else (cons (car l) (insert x (cdr l)))])) (define (sort l) (cond [(empty? l) empty] [else (insert (first l) (sort (rest l)))])) (define (insert x l rel) (cond [(empty? l) (list x)] [(rel x (car l)) (cons x l)] [else (cons (car l) (insert x (cdr l) rel))])) (define (sort l rel) (cond [(empty? l) empty] [else (insert (first l) (sort (rest l) rel) rel)])) © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 184 3.8.1 Funktionen als Parameter Beispiel: > (sort (list 17 3 -1 12 18 22 5 4 9 2) <) (list -1 2 3 4 5 9 12 17 18 22) > (sort (list 17 3 -1 12 18 22 5 4 9 2) >) (list 22 18 17 12 9 5 4 3 2 -1) ;; string>? testet die lexikographische Relation ;; zweier Strings > (sort (list "ab" "a" "abc" "baa") string<?) (list "a" "ab" "abc" "baa") > (sort (list "ab" "a" "abc" "baa") string>?) (list "baa" "abc" "ab" "a") © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 185 3.8.1 Funktionen als Parameter Beispiel: Sortieren von Zahlenpaaren Ein Paar (a1, a2) ist lexikographisch kleiner als ein Paar (b1, b2) wenn a1 < b1 oder (a1 = b1 und a2 < b2) gilt (define (pair<? a b) (or (< (car a) (car b)) (and (= (car a) (car b)) (< (cadr a) (cadr b))))) > (pair<? (list 8 5) (list 7 9)) False > (sort '((3 1) (5 2) (-1 -2) (-1 9)) pair<?) (list (list -1 -2) (list -1 9) (list 3 1) (list 5 2)) (define (pair>=? a b) (not (pair<? a b))) > (sort '((3 1) (5 2) (-1 -2) (-1 9)) pair>=?) (list (list 5 2) (list 3 1) (list -1 9) (list -1 -2)) © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 186 3.8.1 Funktionen als Parameter Beispiel: Sortieren von Listen (define (list<? a b) (cond [(empty? a) true] ;; empty ist kleiner als alles [(empty? b) false] ;; und größer als nichts [else (or (< (first a) (first b)) (and (= (first a) (first b)) (list<? (rest a) (rest b))))])) > (list<? '(2 4 3) '(1 5 7)) false > (sort '((2 4 3) (1 5 7) (3 8 1)) list<?) (list (list 1 5 7) (list 2 4 3) (list 3 8 1)) > (sort '((2 4 3) (1 5 7) (3) (-1 0)) list<?) (list (list -1 0) (list 1 5 7) (list 2 4 3) (list 3)) © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 187 3.8.1 Funktionen als Parameter Funktionale Abstraktion: Viele Daten- und Funktionsdefinitionen sehen ähnlich aus: • Daten: Symbolliste, Zahlenliste, …… • Funktionen: >, <, …… Wiederholungen sind die Quelle vieler Programmfehler Æ Ein wichtigster Schritt beim Schreiben eines Programms besteht in der Elimination von Wiederholungen Den Prozess, zwei ähnliche Funktionen zu einer Definition zusammenzuführen, nennt man funktionale Abstraktion. Funktionen als Parameter zu übergeben bietet eine Möglichkeit dazu. Abstrakte Versionen von Funktionen zu definieren ist sehr nützlich: • Eine einzige Funktion kann viele verschiedene Aufgaben erfüllen • Eine einzige Funktion kann mehrere ähnliche Probleme auf einmal lösen © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 188 3.8.2 Funktionen als Ergebnis Beispiel: Die folgende Funktion quotient gibt zu einer einstelligen Funktion f die Quotientenfunktion zurück, die zu einem vorgegebenen Argument n den Wert 1 / f(n) berechnet: (define (quotient f) (lambda (arg) (/ 1 (f arg)))) > (quotient sqr) (lambda (a1) ...) > ((quotient sqr) 2) 0.25 quotient wird folgendermaßen ausgewertet: ((quotient sqr) 2) → ((lambda (arg) (/ 1 (sqr arg))) 2) → (/ 1 (sqr 2)) → (/ 1 4) → 0.25 © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 189 3.8.2 Funktionen als Ergebnis > (define qsqr (quotient sqr)) > (qsqr 2) 0.25 Bedeutung von lambda-Ausdrücken: Der lambda-Ausduck zur Funktionsdefinition gibt uns eine natürliche Lösung für die Trennung zweier Ebenen: Die Funktion selbst, die mittels lambda abhängig von anderen Größen flexibel definiert und benannt werden kann Die Argumente der definierten Funktion Dadurch kann man beliebig komplexe Funktionen vom Programm aus erzeugen (als Ergebnis von Funktionen) und weiter verwenden © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 190 3.8.2 Funktionen als Ergebnis Beispiel: Für zwei einstellige Funktionen f: A → B und g: B → C wird die Komposition g o f: A → C mit g o f(a) = g(f(a)) durch folgende Funktion realisiert: (define (define (define (define (define (komp f g) (lambda (arg) (g (f arg)))) (mal2 x) (∗ 2 x)) (plus1 x) (+ x 1)) mal2plus1 (komp mal2 plus1)) plus1mal2 (komp plus1 mal2)) (mal2plus1 10) → ((lambda (arg) (plus1 (mal2 arg))) 10) → (plus1 (mal2 10)) → (plus1 20) → 21 (plus1mal2 10) → ((lambda (arg) (mal2 (plus1 arg))) 10) → (mal2 (plus1 10)) → (mal2 11) → 22 © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 191 3.8.2 Funktionen als Ergebnis Beispiel: Die folgende Funktion polynom konstruiert aus den Koeffizienten a0, a1 und a2 die zugehörige Polynomfunktion a0 + a1 ∗ x + a2 ∗ x2 die für ein vorgegebenes x den entsprechenden Wert berechnet (define (polynom a0 a1 a2) (lambda (x) (+ a0 (* a1 x) (* a2 (sqr x))))) >(define p123 (polynom 1 2 3)) ;; p123 = 1 + 2*x + 3*x2 > (p123 2) 17 © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 192 3.8.2 Funktionen als Ergebnis Beispiel: Konstruktion einer approximierenden Ableitungsfunktion nach der Regel f ′( x ) ≈ f ( x + dx ) − f ( x ) dx (define (derive f dx) (lambda (x) (/ (- (f (+ x dx)) (f x)) dx))) > (derive (polynom 0 0 1) 0.00001) 4) 8.00001 © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 193 3.8.2 Funktionen als Ergebnis Ein Vorteil der funktionalen Ausdrucksweise liegt darin, dass man eine Gruppe verwandter Funktionen relativ leicht zu einer gemeinsamen Funktion „abstrahieren“ kann Beispiel: Schnell wachsende Funktionen (define (plus x y) (if (= y 0) x (+ 1 (plus x (- y 1))))) (define (times x y) (if (= y 0) 0 (plus x (times x (- y 1))))) (define (power x y) (if (= y 0) 1 (times x (power x (- y 1))))) (define (super x y) (if (= y 0) 1 (power x (super x (- y 1))))) (define (SUPER x y) (if (= y 0) 1 (super x (SUPER x (- y 1))))) …… © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 194 3.8.2 Funktionen als Ergebnis Dann gilt für die Funktion super: (super x y) = Bsp. (super 2 3) = (y mal) = 16. Die Funktion SUPER wächst noch sehr viel schneller. Die Verallgemeinerung des Musters, nach dem diese Funktionen gestrickt sind, sieht von n = 2 an in funktionaler Schreibweise so aus: ..... (define (power x y) (if (= y 0) 1 (times x (power x (- y 1))))) (define (super x y) (if (= y 0) 1 (power x (super x (- y 1))))) (define (hyper n) (if (= n 2) times (lambda (x y) (if (= y 0) 1 ((hyper (- n 1)) x ((hyper n) x (- y 1))))))) Es gilt (hyper 3) = power, (hyper 4) = super, …… © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 195 3.8.2 Funktionen als Ergebnis > ((hyper 4) 2 3) 16 Die Funktionen (hyper n) wachsen schon für kleine n so schnell, dass jeder Scheme-Interpreter Probleme bekommt. Der Nutzen liegt eher in der Theoretischen Informatik. Wenn man die Argumente x, y mit n zusammen wachsen lässt, erhält man eine extrem schnell wachsende Funktion: (define (ackermann n) ((hyper n) n n)) Es gilt: (ackermann 1) = (plus 1 1) = 2 (ackermann 2) = (times 2 2) = 4 (ackermann 3) = (power 3 3) = 3^3 = 27 (ackermann 4) = (super 4 4) = 4^4^4^4 Die letzte Zahl hat bereits ca. 10153 Stellen (nicht: 153 Stellen!) © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 196 3.8.2 Funktionen als Ergebnis Diese (genauer: eine sehr ähnliche) Funktion wurde von Wilhelm Ackermann (1896 – 1962) im Jahr 1926 konstruiert. Ackermann war Assistent von David Hilbert in Göttingen, später Lehrer in Burgsteinfurt und Honorarprofessor an der WWU Münster. Anwendung in der Theoretischen Informatik: Die Ackermannsche Funktion ist berechenbar, aber nicht primitiv rekursiv. Damit widerlegte Ackermann eine Vermutung von Hilbert. © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 197 3.8.2 Funktionen als Ergebnis Zusammenfassung: „First class“–Funktionen erhöhen die Ausdruckskraft einer Programmiersprache und ermöglichen die Beschreibung komplexer Zusammenhänge in sehr kompakter Form Der Preis dafür ist ein höheres Abstraktionsniveau im Denken: Funktionen, die nur auf elementaren Daten wie Zahlen etc. arbeiten, sind leichter zu verstehen, als Funktionen, die andere Funktionen verarbeiten und/oder erzeugen © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 198 3.9 Blockstrukturen: Umgebungen und Scoping Umgebung (Kontext): Der Scheme-Interpreter unterhält eine Art Speicher, um Name-Wert Paare zu verwalten (vgl. 3.3.2. Bindungen und Umgebungen) Start in einer Umgebung, die alle vordefinierten Basisfunktionen / Namen Durch define werden weitere Namen an einfache Werte oder Funktionen gebunden, Ausdrücke ausgewertet, Namen re-definiert (erneutes define für schon gebundene Namen), …… Zu jedem Zeitpunkt hat man in der Umgebung eine gerade aktuelle Menge von Namen mit Bindungen für Werte und Funktionen, die sich aufeinander abstützen Diese veränderlichen Bindungen bilden die globale Umgebung © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 199 3.9 Blockstrukturen: Umgebungen und Scoping Probleme der globalen Umgebung: Wenn die Anzahl der Funktionen zu groß wird, verliert man schnell den Überblick Jede Prozedur „kostet“ einen Namen, d.h., der Namensraum für Funktionen wird immer kleiner; Gefahr von Verwechselungen oder Namenskonflikten wächst Fehlen von lokal definierten Variablen macht den Code unübersichtlich Da alle Funktionen überall in anderen Funktionen aufgerufen werden können, müssen sie gegen mögliche falsche Verwendungen abgesichert werden. Die globale Verfügbarkeit stellt generell eine permanente Fehlerquelle dar. Daher gibt es die Möglichkeit lokaler Umgebungen, um lokale Namen zu definieren und an Werte oder Prozeduren zu binden © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 200 3.9.1 Lokale Variablen Notwendikeit lokaler Variablen: Scheme-Interpreter wertet zuerst die Argumente einer Funktion aus, mit den so berechneten Bindungen für die formalen Parameter wird dann der Funktionsrumpf ausgewertet Æ Häufig mehrfache Auswertung erforderlich f ( x, y ) = x ⋅ (1 + x ⋅ y ) 2 + y ⋅ (1 − y ) 2 + (1 + x ⋅ y ) ⋅ (1 − y ) (define (f x y) (+ (* x (sqr (+ 1 (* x y)))) (* y (sqr (- 1 y))) (* (+ 1 (* x y)) (- 1 y)))) Mehrfache Auswertung von a und b wird verhindert durch Substituition: a = 1 + x ⋅ y ,b = 1 − y ⇒ f ( x, y ) = x ⋅ a 2 + y ⋅ b2 + a ⋅ b © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 201 3.9.1 Lokale Variablen Realisierung durch lambda-Ausdruck: Macht man einen mehrfach vorkommenden Ausdruck zum Argument eines lambda-Ausdrucks, so wird seine Mehrfachauswertung vermieden f ( x, y ) = x ⋅ (1 + x ⋅ y ) 2 + y ⋅ (1 − y ) 2 + (1 + x ⋅ y ) ⋅ (1 − y ) a = 1 + x ⋅ y ,b = 1 − y ⇒ f ( x, y ) = x ⋅ a 2 + y ⋅ b2 + a ⋅ b (define (f x y) ( (lambda (a b) (+ (* x (sqr a)) (* y (sqr b)) (* a b))) (+ 1 (* x y)) (- 1 y) ) ) © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 202 3.9.1 Lokale Variablen Kurzform durch special form let: (define (f x y) (let ( (a (+ 1 (∗ x y))) (b (- 1 y))) (+ (∗ x (sqr a)) (∗ y (sqr b)) (∗ a b)))) (a (+ 1 (∗ x y))) und (b (- 1 y)) sind sog. Bindungspaare Allgemeine Form von let in Scheme: (let ((<var1> <exp1>) … (<varn> <expn>)) <body>) Liste von Bindungspaaren ist äquivalent zu ((lambda (<var1> … <varn>) <body>) <exp1> … <expn>) © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 203 3.9.1 Lokale Variablen Auswertung von let: (let ((<var1> <exp1>) … (<varn> <expn>)) <body>) 1. Erst (!) alle <exp1> … <expn> auswerten zu <val1> … <valn> 2. Danach (!) alle <var1> … <varn> binden an <val1> … <valn> in der Form ((<var1> <val1>) … (<varn> <valn>)) (∗) 3. <body> wird in der lokalen Umgebung mit den Bindungen (∗) ausgewertet, der Wert von <body> wird als Wert von let zurückgegeben 4. Danach sind die lokalen Bindungen nicht mehr zugreifbar © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 204 3.9.1 Lokale Variablen Achtung: Eine Variable <vari> ist noch nicht an <vali> gebunden, wenn <expj> mit j > i ausgewertet wird Æ <expj> darf nicht auf <vari> zugreifen! Beispiel: (define a 100) > (let ((a 10) (b (∗ 2 a))) (+ a b)) 210 > (∗ 2 b) reference to an identifier before its definition: b © Xiaoyi Jiang Informatik I – Grundlagen der Programmierung 205 3.9.1 Lokale Variablen Alternative special form let* mit modifizierter Auswertung: <vari> wird an <vali> gebunden, bevor <expi+1> ausgewertet wird (let* ((<var1> <exp1>) (<var2> <exp2>) …… (<varn> <expn>)) <body>) Beispiel: > (define a 100) > (let* ((a 10) (b (∗ 2 a))) (+ a b)) > 30 ;; ;; ;; ;; © Xiaoyi Jiang Bei Auswertung (+ a b) ist a bereits gebunden mit a=10; Bindng a=100 nicht relevant Informatik I – Grundlagen der Programmierung 206