3 Exkurs: Der λ-Kalkül Alonso Churchs λ-Kalkül (ca. 1940) ist der formale Kern jeder funktionalen Programmiersprache. Der λ-Kalkül I ist eine einfache Sprache, mit nur wenigen syntaktischen Konstrukten und simpler Semantik. (⇒ Eine Implementation des λ-Kalküls ist leicht zu erhalten und damit eine gute Basis für die Realisierung von FPLs.) I ist eine mächtige Sprache. Jede berechenbare Funktion kann im λ-Kalkül dargestellt werden. (Alle Konzepte, auch die moderner FPLs, lassen sich auf den λ-Kalkül abbilden. Im Prinzip könnten wir einen Compiler für eine FPL erhalten, indem wir sie auf den λ-Kalkül abbilden und dessen Implementation nutzen.) © 2003 T. Grust · Funktionale Programmierung: 3. Exkurs: Der λ-Kalkül 23 FPL ←→ λ-Kalkül. Funktionales Programm Ausführung des Programms ≡ ≡ Ausdruck des λ-Kalküls Auswertung des Ausdrucks durch Reduktion Primitiver Algorithmus zur Auswertung: O 1 Wähle nächsten zu reduzierenden Teilausdruck e (Redex, reducible expression). O 2 Ersetze Redex e durch Reduktionsergebnis e0 . O 3 Stop, wenn Normalform erreicht. Sonst zurück zu O 1. 2 ab jetzt (sprich: e wird zu e0 reduziert, e reduces to e0 ): Schreibe für Schritt O e _ e0 1 und O 3 ) sind Thema des Kapitels 12. Auswertungsverfahren (insb. die Schritte O © 2003 T. Grust · Funktionale Programmierung: 3. Exkurs: Der λ-Kalkül 24 Beispiel 3.1 Gegeben sei der λ-Ausdruck: (+ (× 5 6) (× 8 3)) Auswertung durch Reduktion: Schritt λ-Ausdruck Aktion O 1 Zwei Redexe: O 2 Reduktion: O 3 Normalform? Nein, ≥ 1 Redex verbleibt. (+ 30 (× 8 3)) O 1 Wähle einzigen Redex (+ 30 (× 8 3)) O 2 Reduktion: O 3 Normalform? Nein, ≥ 1 Redex verbleibt. (+ 30 24) O 1 Wähle einzigen Redex (+ 30 24) O 2 Reduktion: (+ 30 24) _ 54 54 O 3 Normalform? Ja, kein Redex verbleibt. Stop. 54 (× 5 6) und (× 8 3). Wähle ersten (beliebig). (× 5 6) _ 30 (× 8 3) (× 8 3) _ 24 (+ 30 24) © 2003 T. Grust · Funktionale Programmierung: 3. Exkurs: Der λ-Kalkül (+ (× 5 6) (× 8 3)) (+ 30 (× 8 3)) (+ 30 24) 25 3.1 Syntax des λ-Kalküls Funktionsanwendung. Juxtaposition von Funktion und Argument, notiert in Prefix-Form: (f x) Currying. Anwendung von f auf mehr als 2 Argumente? Beispiel: Summe von x und y: ((+ x) y) I Ausdruck (+ x) ist die Funktion, die x zu ihrem Argument addiert, (Ein funktionswertiger Ausdruck!) I die dann auf y angewandt wird. Mittels Currying kann jede Funktion als Funktion eines einzigen Arguments verstanden werden. Vereinbarung. Funktionsanwendung ist links-assoziativ. Schreibe daher auch kürzer (+ x y). © 2003 T. Grust · Funktionale Programmierung: 3. Exkurs: Der λ-Kalkül 26 3.1.1 Konstante und vordefinierte Funktionen Der Kern des λ-Kalküls bietet keine Konstanten (wie 1, "foo", True) oder vordefinierte Funktionen (wie +, ×, if). In dieser Vorlesung: um Konstante und entsprechende Reduktionsregeln erweiterter λ-Kalkül. Beispiel 3.2 Reduktion einiger vordefinierter Funktionen (später: δ-Reduktion _ , siehe Abschnitt 3.3): δ (+ x y) (× x y) (if True e f ) (if False e f ) (and False e) (and True e) _ _ _ _ _ _ x y x y e f False e (Operationen in 2 seien direkt auf der Zielmaschine ausführbar, wenn die Argumente x und y zuvor bis zur Normalform reduziert wurden) © 2003 T. Grust · Funktionale Programmierung: 3. Exkurs: Der λ-Kalkül 27 3.1.2 λ-Abstraktionen Mit den sog. λ-Abstraktionen werden im λ-Kalkül neue Funktionen definiert: λx.e In dieser λ-Abstraktion ist x der formale Parameter, der im Funktionskörper e zur Definition der Funktion benutzt werden kann (sprich: x ist in e gebunden, siehe Abschnitt 3.2.1). Der Punkt ‘.’ trennt x und e. Beispiel 3.3 Funktion, die ihr Argument inkrementiert: λx.(+ x 1) Maximumsfunktion: λx.(λy.(if (< x y) y x)) © 2003 T. Grust · Funktionale Programmierung: 3. Exkurs: Der λ-Kalkül 28 3.1.3 Syntax des λ-Kalküls (Grammatik) Kontextfreie Grammatik, die syntaktisch korrekte λ-Ausdrücke beschreibt: e → | | | c v (e e) (λv.e) Konstanten, vordef. Funktionen Variablen Applikation (Juxtaposition) λ-Abstraktion Treten keine Mehrdeutigkeiten auf, können Klammern (·) weggelassen werden. Beispiel 3.4 ((λx.(+ x 1)) 4) ≡ (λx.(+ x 1)) 4 © 2003 T. Grust · Funktionale Programmierung: 3. Exkurs: Der λ-Kalkül ≡ (λx. + x 1) 4 29 3.2 Operationale Semantik des λ-Kalküls Als zweiten Schritt definieren wir die Bedeutung (Semantik) eines λ-Ausdrucks und legen damit fest, wie man mit λ-Termen „rechnen“ kann. 3.2.1 Freie und gebundene Variablen Um den folgenden λ-Ausdruck auszuwerten (λx. + x y) 4 I wird der hier nicht bekannte „globale“ Wert der Variablen y benötigt, während I der Wert des Parameters x im Funktionskörper (+ x y) durch das Argument 4 festgelegt ist. Innerhalb der λ-Abstraktion I ist der Parameter x (durch das λ) gebunden, während I die Variable y frei ist. © 2003 T. Grust · Funktionale Programmierung: 3. Exkurs: Der λ-Kalkül 30 Definition 3.1 Eine Variable x ist in einem Ausdruck frei bzw. gebunden: x ist frei in v ⇐⇒ x ist frei in (e e0) ⇐⇒ x ist frei in (λy.e) ⇐⇒ x=v (nicht wenn x 6= v oder v = c) x ist frei in e oder x ist frei in e0 x 6= y und x ist frei in e x ist gebunden in (e e0 ) ⇐⇒ x ist gebunden in (λy.e) ⇐⇒ x ist gebunden in e oder x ist gebunden in e0 (x = y und x ist frei in e) oder x ist gebunden in e (in einem Ausdruck, der aus einer einzigen Variablen v bzw. Konstanten c besteht, ist x nie gebunden) © 2003 T. Grust · Funktionale Programmierung: 3. Exkurs: Der λ-Kalkül 31 Generell hängt der Wert eines Ausdrucks nur von seinen freien Variablen ab. Beispiel 3.5 In der λ-Abstraktion λx. + ((λy. × y z) 7) x sind x und y gebunden, z jedoch frei. Vorsicht! Bindung/Freiheit muß für jedes Auftreten einer Variable entschieden werden. Ein Variablenname kann innerhalb eines Ausdrucks gebunden und frei auftreten. Beispiel 3.6 + x ((λx. + x 1) 4) O 1 O 2 1 tritt x frei auf, während x an Position O 2 gebunden vorliegt. Bei O © 2003 T. Grust · Funktionale Programmierung: 3. Exkurs: Der λ-Kalkül 32 3.2.2 β-Reduktion Eine λ-Abstraktion beschreibt eine Funktion. Wir definieren hier, wie eine λ-Abstraktion auf ein Argument angewandt wird. Definition 3.2 Die Funktionsanwendung (λx.e) M wird reduziert zu I einer Kopie des Funktionsrumpfes e, in der I die freien Vorkommen von x durch M ersetzt wurden. Beispiel 3.7 Damit gilt (λx. + x 1) 4 _ β +41 _ 41 _ 5 Die Reduktion _ ist die sog. β-Reduktion des λ-Kalküls. β © 2003 T. Grust · Funktionale Programmierung: 3. Exkurs: Der λ-Kalkül 33 Beispiel 3.8 Der formale Parameter kann mehrfach oder gar nicht im Funktionsrumpf auftreten: (λx. + x x) 5 _ (λx.3) 5 _ +55 β _ 10 3 β In einem Funktionsrumpf kann eine weitere λ-Abstraktion enthalten sein (Currying in Aktion, vgl. Abschnitt 3.1): (λx.(λy. × y x)) 4 5 _ β _ β _ (λy. × y 4) 5 ×54 20 Wir werden im folgenden λx.(λy.e) oft durch λx.λy.e oder gar λx y.e abkürzen. © 2003 T. Grust · Funktionale Programmierung: 3. Exkurs: Der λ-Kalkül 34 Beispiel 3.9 Funktionen können problemlos als Argumente übergeben werden: (λf.f 3) (λx. + x 1) _ β _ β _ (λx. + x 1) 3 +31 4 Bei β-Reduktion werden nur die freien Vorkommen des formalen Parameters im Funktionsrumpf ersetzt: (λx.(λx. + (− x 1)) x 3) 9 O 1 _ β _ β _ (λx. + (− x 1)) 9 3 + (− 9 1) 3 11 1 ist durch die innere λ-Abstraktion gebunden und wird daher bei der ersten Das Vorkommen von x bei O β-Reduktion nicht ersetzt. © 2003 T. Grust · Funktionale Programmierung: 3. Exkurs: Der λ-Kalkül 35 3.2.3 α-Konversion Die Bedeutung einer λ-Abstraktion ändert sich nicht, wenn wir ihre formalen Parameter konsistent umbenennen, d.h. alle gebundenen Vorkommen des Parameters ebenfalls umbenennen: (λx. × x 2) ] α (λy. × y 2) Manchmal ist diese α-Konversion unerläßlich, um Namenskollisionen und damit fehlerhafte Reduktionen (sog. „name capture“) zu vermeiden. Beispiel 3.10 Unter dem Namen TWICE sei folgende Funktion definiert (die ihr erstes Argument zweimal auf ihr zweites anwendet): TWICE ≡ (λf.λx.f (f x)) Wir verfolgen jetzt die Reduktion des Ausdrucks (TWICE TWICE ) mittles β-Reduktion. (TWICE TWICE ) _ ≡ _ β (λf.λx.f (f x)) TWICE (λx. TWICE (TWICE | {z x})) O 2 | {z } O 1 © 2003 T. Grust · Funktionale Programmierung: 3. Exkurs: Der λ-Kalkül 36 1 und O 2 . Wir wählen O 2 beliebig: Es entstehen die Redexe O _ ≡ _ β (λx.TWICE ((λf.λx.f (f x)) x)) (λx.TWICE (λx.x (x x))) falsch Die x sind nun fälschlicherweise durch die innere λ-Abstraktion gebunden (captured). Umbennunng 2: mittels α-Konversion hilft hier. Nach Reduktion von O _ ≡ ] α _ β (λx.TWICE ((λf.λx.f (f x)) x)) (λx.TWICE ((λf.λy.f (f y)) x)) (λx.TWICE (λy.x (x y))) Beispiel 3.10 © 2003 T. Grust · Funktionale Programmierung: 3. Exkurs: Der λ-Kalkül 37 3.3 Zusammenfassung Probleme wie name capture können die Anwendung der ansonsten simplen β-Reduktionsregel verkomplizieren. Wir geben hier daher eine endgültige formale Definition der β-Reduktion, die derartige Probleme berücksichtigt. Definition 3.3 β-Reduktion: (λx.e) M _ β e[M/x] e[M/x] (sprich: M für x in e) ist definiert durch v[M/x] x[M/x] (e e0 )[M/x] (λx.e)[M/x] (λv.e)[M/x] = = = = = v M (e[M/x] e0[M/x]) λx.e λv.e[M/x] = λz.(e[z/v])[M/x] wenn v 6= x oder v = c wenn x nicht frei in e ist oder v nicht frei in M ist sonst (z neuer Variablenname) © 2003 T. Grust · Funktionale Programmierung: 3. Exkurs: Der λ-Kalkül 38 Damit sind alle Werkzeuge zum Umgang mit dem λ-Kalkül zusammengestellt. Operationale Semantik des λ-Kalküls: α-Konversion λx.e ] β-Reduktion (λx.e) M _ δ-Reduktion (∗ e) _ α β δ λy.e[y/x] e[M/x] wenn y nicht frei in e ist e implementiert ∗ auf der Zielmaschine Dieses kompakte formale System ist ausreichend, um als Zielsprache für alle funktionalen Programmiersprachen zu dienen. I Tatsächlich ist Haskell ein syntaktisch stark angereicherter λ-Kalkül. I Sprachelemente von Haskell werden wir teilweise auf den λ-Kalkül zurückführen. © 2003 T. Grust · Funktionale Programmierung: 3. Exkurs: Der λ-Kalkül 39