TIDS 2, SS13, Kapitel 1, vom 11.4.2013 1 1 Vollständige partielle Ordnungen, stetige Funktionen Die Betrachtung vollständiger partieller Ordnungen soll eine einfache und verständliche Einführung in einen Ansatz zur denotationellen Semantik liefern. Die Sprache, die wir dazu betrachten werden, ist PCF, eine Variante einer funktionalen Programmiersprache. Polymorph getypte funktionale Programmiersprachen und deren denotationelle Semantik würden den Rahmen dieser Vorlesung sprengen. Definition 1.1 Eine partielle Ordnung ≤ auf einer Menge P ist definiert durch die Eigenschaften: • reflexiv: ∀x ∈ P : x ≤ x • transitiv: ∀x, y, z ∈ P : x ≤ y, y ≤ z =⇒ x ≤ z • antisymmetrisch ∀x, y, ∈ P : x ≤ y ∧ y ≤ x =⇒ x = y Definition 1.2 Sei X ⊆ P . • Sei p ∈ P . Wenn ∀x ∈ X : x ≤ p dann ist p obere Schranke von X. • p ∈ P ist eine kleinste obere Schranke (obere Grenze, lub, least upper bound) von X, wenn p obere Schranke von X undFfür jede andere obere Schranke q von X gilt p ≤ q. Bezeichnung: p = X. • Analog kann man untere Schranken, glb, untere Grenze usw. definieren Definition 1.3 Eine Folge a1 ≤ a2 ≤ a3 ≤ . . . mit Elementen ai ∈ P heißt Kette (ω-Kette). • Die partielle Ordnung ≤ auf P ist vollständig, wenn jede aufsteigende Kette a1 ≤ aF 2 ≤ a3 ≤ . . . eine kleinste obere Schranke in P hat. Bezeichnung: i ai . • Wenn die partielle Ordnung P ein kleinstes Element ⊥ hat, dann sagt man, ≤ ist eine Ordnung mit kleinstem Element. • Als Abkürzung sprechen wir bei einer vollständig partiellen Ordnung mit kleinstem Element von einer cpo mit ⊥. Wenn die Bezeichnung ⊥ nicht eindeutig ist, indizieren wir das ⊥ mit der zugehörigen cpo. Wir beschäftigen uns im folgenden mit vollständigen partiellen Ordnungen (cpo’s). Beispiel 1.4 TIDS 2, SS13, Kapitel 1, vom 11.4.2013 2 • Die Potenzmenge einer Menge mit ⊆ ist eine cpo mit kleinstem Element. Für F S eine aufsteigende Kette M1 ⊆ M2 ⊆ M3 ⊆ . . . gilt i Mi . i Mi = • Die Menge der partiellen Funktionen: M → M mit der Teilmengenbeziehung ist eine cpo mit kleinstem Element. • Die zweielementige Menge {⊥, >} mit ⊥ ≤ > ist eine cpo. Eine diskrete (flache) cpo ist eine partielle Ordnung, in der nur x ≤ x für alle Elemente x gilt. Definition 1.5 Monotonie Eine Funktion f : D → E, wobei D, E cpo’s sind, ist monoton, gdw. für alle d1 ≤D d2 gilt, dass auch f (d1 ) ≤E f (d2 ). Stetigkeit Die Funktion f : D → E ist stetig, wenn sie monotonFist und wenn für alle Ketten d1 ≤D d2 ≤D d3 ≤D . . . gilt, dass f ( i di ) = F i f (di ). Bemerkung 1.6 Die Intuition dahinter ist: stetig = algorithmisch sinnvoll Bei Anwendung auf eine Programmiersprache wird sich ergeben, dass alle berechenbaren Funktionen eine stetige Denotation haben. In manchen Semantiken kommt es vor, dass es stetige Funktionen gibt, die nicht Denotation einer Funktion sind. Im folgenden bezeichnen wir für eine Funktion f : D → D. die mehrfache Anwendung f (. . . f (d) auch als f n (d). | {z } n Beispiel 1.7 Alle Funktionen, die auf einer diskreten cpo definiert sind, sind stetig. Eine wichtige Beobachtung, die in ähnlicher Form oft verwendet wird zur Konstruktion kleinster Fixpunkte, ist folgendes: In einer cpo mit ⊥ sei eine monotone Funktion f gegeben. Dann ist f i (⊥) eine Kette. Es gilt ⊥ ≤ f (⊥) ≤ f (f (⊥)) ≤ . . . ≤ f n (⊥) ≤ f n+1 (⊥) ≤ . . .. Aussage 1.8 • Die Identität auf einer cpo ist stetig • Die Komposition von stetigen Funktionen ist stetig. D.h. f ◦g ist stetig, wenn f, g stetig sind. TIDS 2, SS13, Kapitel 1, vom 11.4.2013 3 Beweis. Die Identität ist monoton. Da eine Kette unter Id auf sich selbst abgebildet wird, gilt das auch für den lub der Kette(n). Der Rest ist eine Übungsaufgabe. 2 Definition 1.9 Sei f : D → D eine Funktion, wobei D eine cpo ist. Dann ist d ∈ D ein • Fixpunkt von f , wenn f (d) = d ist. • Präfix-punkt von f , falls f (d) ≤ d • Postfix-punkt von f , falls d ≤ f (d). Satz 1.10 (Fixpunktsatz) Sei f : D → D eine stetige Funktion, wobei D eine cpo mit kleinstem Element ⊥ ist. Dann hat f F einen Fixpunkt, insbesondere einen kleinsten Fixpunkt a. Zudem gilt a = f n (⊥). a ist auch der kleinste Präfix-punkt von f . Diesen kleinsten Fixpunkt nennen wir f ix(f ). F Beweis.FDa D eine cpo ist, existiert f n (⊥)). Es gilt: f ( F f n (⊥)) = f n+1 (⊥)) (Stetigkeit von f und da ⊥ ≤ f (⊥) ≤ ...f n (⊥) . . . aufsteigende Kette ist) F = f n (⊥)) da f (⊥) ≥ ⊥ und F nes bis auf ⊥ die gleiche Kette ist. Damit haben wir gezeigt, dass f (⊥)) ein Fixpunkt von f ist. Jetzt zeigen wir, dass dies auch der kleinste ist. Sei d irgendein Fixpunkt von f . D.h. f (d) = d. Dann gilt: ⊥ ≤ d, also auch f (⊥) ≤ d = f (d), da f Fmonoton ist. Mit Induktion nach n erhält man: f n (⊥) ≤ d, also auch f n (⊥)) ≤ d, da F n f (⊥)) kleinste obere Schranke der aufsteigenden Kette ⊥ ≤ f (⊥) ≤ ...f n (⊥) . . . ist. F Dieselbe Argumentation zeigt auch dass f n (⊥)) kleinster Präfixpunkt von f ist. 2 Es gilt folgender allgemeinerer Satz (siehe z.B. Davey und Priestley) Satz 1.11 Sei f : D → D eine monotone Funktion, wobei D eine cpo ist. Dann hat f einen kleinsten Fixpunkt. Allerdings ist der kleinste Fixpunkt in diesem allgemeinen Fall i.a. nicht als F n f (⊥) konstruierbar, auch wenn es ein kleinstes Element ⊥ gibt. Beispiel 1.12 Für eine nicht-monotone Funktion. Gegeben sei die Boolesche cpo: {True, False, ⊥} mit ⊥ ≤ True, ⊥ ≤ False. Die Funktion, die Terminierung testet ist: TIDS 2, SS13, Kapitel 1, vom 11.4.2013 4 f : ⊥ → False, True → True, False → True. Diese Funktion f ist nicht monoton. Denn: ⊥ ≤ True, aber f ⊥ = False 6≤ True = f True. Beispiel 1.13 Für eine monotone, aber nicht stetige Funktion. Betrachte unendliche Strings (oder alternativ unendliche Listen). Wir wollen Eingabeströme modellieren, wobei diese endlich oder unendlich sein können, und nur aus 0, 1, bestehen. Modellierung: Als Menge benutzen wir (endliche und unendliche) Strings bestehend aus 0, 1 und $, wobei $ nur am Ende sein darf. Endliche Strings ohne $ am Ende betrachtet man als unvollständige Strings (bzw. Eingabe). Formal könnte man schreiben S := ({0, 1}∗ + {0, 1}∗ $) ∪ { alle unendlichen Folgen bestehend aus 0,1 }. ε bezeichne den leeren String. Als Ordnung verwenden wir s ≤ t, wenn s Präfix von t. Maximale Strings in S sind die unendlichen und mit $ beendete Strings. Die Menge S ist eine cpo mit kleinstem Element ε: Vollständigkeit: interessant sind nur die unendlichen (echten) Folgen. Deren lubs sind unendliche Strings. Wir betrachten eine Funktion auf S, die durch Durchlesen des Strings von links nach rechts feststellt, ob eine 1 in einem String ist oder nicht: hateins : S → {True, False} Das Problem ist jetzt, dass diese Funktion zwar leicht ja sagen kann, aber nein nur, wenn sie bis zum $ keine 1 gefunden hat. Sind bis zu einem Zeitpunkt nur 0’en aufgetaucht, so gibt es noch keine Antwort. hateins(ε) bedeutet, dass im Moment auf weitere Eingabe gewartet werden muß. Im schlechtesten Fall gibt diese Funktion keine Antwort: wenn der unendlich lange String aus 0’en die Eingabe ist. Entweder ist dann unsere Funktion partiell oder wir brauchen einen Wert “unbekannt“. Da wir bereits festgestellt haben, dass partielle Funktionen auch als totale Funktionen auf einem mit ⊥ erweiterten Bereich modelliert werden können, nehmen wir die zweite Alternative: {True, False, ⊥} mit ⊥ ≤ True und ⊥ ≤ False. Die Anforderungen an hateins sind: hateins(1s) = True hateins($) = False hateins(0s) = hateins(s) hateins(ε) = ⊥ Diese bestimmen den Wert für den unendlichen String aus Nullen 000 . . . = 0ω nicht eindeutig. Sinnvoll wären False oder ⊥. Der logisch richtige Wert wäre False, aber dieser läßt sich algorithmisch erst nach unendlich vielen Überprüfungen bestimmen. TIDS 2, SS13, Kapitel 1, vom 11.4.2013 5 Betrachtet man das Verhalten der Funktionen bzgl. der cpo, dann korrespondiert das “richtige“ Berechnungsverhalten zur Stetigkeit der Funktion hateins. 00 . . . läßt sich approximieren durch die aufsteigende Folge: ε, 0, 00, 000, . . .. Der lub davon ist gerade 0ω . Der jeweilige Wert von hateins(ε), hateins(0), . . . . . . ist ⊥. Stetigkeit bedeutet dann, dass hateins(0ω ) = ⊥. Die Funktion hateins0 mit hateins0 (0ω ) = False wäre nicht mehr stetig, da False nicht der lub der approximierenden Folge ist. 1.0.1 Vollständige Verbände, Knaster-Tarski Fixpunktsatz Wir erwähnen noch den Fixpunktsatz für vollständige Verbände. Definition 1.14 • Eine Ordnung ≤ auf D ist ein Verband, gdw, für beliebige Elemente x, y ∈ D sowohl glb(x, y) als auch lub(x, y) in D existieren. • Eine Ordnung ≤ auf D ist ein vollständiger Verband gdw. für beliebige Teilmengen A ⊆ D sowohl glb(A) als auch lub(A) existiert (eindeutig). Ein vollständiger Verband ist insbesondere eine cpo mit ⊥, denn jeder vollständige Verband hat ein kleinstes und ein größtes Element. Die Potenzmenge P(M ) einer Menge vollständiger VerS M ist immer einT band bzgl ⊆. lub und glb sind durch {Mi } bzw. durch {Mi } definiert. In einem Verband genügt die Monotonie einer Funktion zur Existenz eines Fixpunktes. Der ist auch einfach zu konstruieren. Satz 1.15 (Knaster, Tarski) Sei D ein Verband und Φ : D → D eine monotone Abbildung. Dann existiert ein kleinster und größter Fixpunkt von V Φ: der kleinste Fixpunkt ist {x | Φ(x) ≤ x}. V Beweis. Sei H := {x ∈ D | Φ(x) ≤ x} und sei α = H. Für alle x ∈ H gilt α ≤ x, also gilt Φ(α) ≤ Φ(x) ≤ x (Monotonie). Deshalb ist Φ(α) untere Schranke von H; somit Φ(α) ≤ α. Aus Φ(α) ≤ α folgt jetzt Φ(Φ(α)) ≤ Φ(α) und damit nach Definition von H auch Φ(α) ∈ H D.h. aber, α ≤ Φ(α) und somit folgt aus der Antisymmetrie: α = Φ(α). D.h. α ist ein Fixpunkt. Da alle Fixpunkte β per Definition in H sind, muß α kleinster Fixpunkt von Φ sein. 2 TIDS 2, SS13, Kapitel 1, vom 11.4.2013 1.1 6 Konstruktion neuer cpo’s Wir konstruieren neue vollständige partielle Ordnungen, um aus einfachen semantischen Bereichen, wie z.B. dem der natürlichen Zahlen und der Booleschen Werte neue cpos, d.h. neue semantische Bereiche zu konstruieren. Wir werden sehen, dass den Konstruktionsverfahren jeweils bestimmte Programmkonstrukte entsprechen. Zunächst benötigen wir den Begriff der Isomorphie zweier cpo’s: Definition 1.16 Seien D, E zwei cpo’s. Eine stetige Funktion f : D → E ist ein Isomorphismus, wenn sie eine Bijektion ist und wenn es eine stetige, bijektive Funktion g : E → D gibt, so dass f ◦ g = IdE und g ◦ f = IdD ist. D und E heißen dann isomorph. I. a. unterscheiden wir isomorphe cpo’ s nicht, da sie im wesentlichen dasselbe Objekt sind. Aussage 1.17 Seien D, E zwei cpo’s. Eine Funktion f : D → E ist ein Isomorphismus, gdw. f eine Bijektion ist mit x ≤D y gdw. f (x) ≤E f (y). Beweis. Die eine Richtung ist offensichtlich. Sei f : D → E eine Bijektion mit x ≤D y gdw. f (x) ≤E f (y). Sei g die inverse Funktion zu f . Aus der Voraussetzung folgt, dass g ebenfalls die Monotonie-Eigenschaft von f hat. Sei a1 ≤D a2 ≤D . . . eine aufsteigende Kette und sei a die kleinste obere Schranke zu ai . Dann existiert zu f (a1 ) ≤E f (a2 ) ≤E . . . eine kleinste obere Schranke e. Da f (ai ) ≤E f (a) für alle i, gilt e ≤ f (a). Wegen der Voraussetzung ist ai ≤ g(e) für alle i, d.h. g(e) ist obere Schranke der ai mit g(e) ≤ a. Da a obere Grenze ist, gilt g(e) = a und damit auch e = f (a). D.h. f ist stetig. Die Funktion g ist ebenfalls stetig, da g die Voraussetzung der Proposition erfüllt. 2 1.2 Diskrete cpo’s In diskreten cpo’s gilt nur x ≤ x für alle x ∈ D. Jede Kette ist konstant: x ≤ x ≤ . . .. Diese cpo’s kommen vor bei Booleschen Wahrheitswerten, oder der Menge der Zahlen. Die Zahlen sind bzgl. Informationsgehalt unvergleichbar, da sie jeweils den maximalen Informationsgehalt repräsentieren. Es gilt: Jede Funktion f : D → E ist stetig, wenn D eine diskrete cpo ist. 1.3 Endliche Produkte. Seien Di , i = 1, . . . , k cpo’s. Wir können immer annehmen, dass die Mengen disjunkt sind. D1 × . . . × Dk ist das zugehörige cartesische Produkt (die Menge der k-Tupel). Die Ordnung darauf ist punktweise definiert: 7 TIDS 2, SS13, Kapitel 1, vom 11.4.2013 (d1 , . . . , dk ) ≤ (d01 , . . . , d0k ) gdw. di ≤ d0i für alle i. F Es gilt für Ketten von Tupeln (d1j , . . . , dkj ), dass j (d1j , . . . , dkj ) = F F ( j d1j , . . . , j dkj ). (Übungsaufgabe) Die Projektionsfunktionen πi sind definiert durch πi (d1 , . . . , dk ) = di . Diese Projektionsfunktionen sind stetig. Tupelbildung kann man für Funktionen ebenfalls verwenden: Seien fi : E → Di , i = 1, . . . , k stetige Funktionen auf den cpo’s E, Di . Dann ist die Funktion hf1 , . . . , fk i : E → (D1 , . . . , Dk ) definiert durch hf1 , . . . , fk i e := (f1 (e), . . . , fk (e)). Die Funktion hf1 , . . . , fk i erfüllt πi ◦ hf1 , . . . , fk i = fi . Die Funktion hf1 , . . . , fk i ist offenbar monoton. Sie ist auch stetig: Sei e0 ≤ e1 ≤ e2 ≤ . . . eine Kette in E. = = = = F hf1 , F . . . , fk i( i ei )F (fF1 ( i ei ), . . . , fF k ( i ei )) (F i (f1 (ei ), . . . , i fk (ei )) da alle fi stetig sind (Definition des Produktes) Fi ((f1 (ei ), . . . , fk (ei )) hf , . . . , f i(e ) Definition von h. . .i i 1 k i Die Produktbildung kann man für Funktionen und Kreuzprodukt von cpo’s gemeinsam machen: Seien fi : Di → Ei stetige Funktionen auf cpo’s. Dann ist f1 × . . . × fk : D1 × . . . × Dk → E1 × . . . × Ek definiert durch f1 × . . . × fk (d1 , . . . , dk ) = (f1 (d1 ), . . . , fk (dk )). Dies kann man schreiben als f1 × . . . × fk = hf1 ◦ π1 , . . . , fk ◦ πk i. Daraus kann man schließen, dass diese Funktion ebenfalls stetig ist. Lemma 1.18 Sei h : E → D1 × . . . × Dk eine Funktion auf cpo’s. Dann ist h stetig gdw. πi ◦ h stetig ist für alle i. Beweis. “ =⇒ “: Komposition ist stetig. “⇐“: Nehme an, dass πi ◦ h stetig ist für alle i. Dann kann man h aus stetigen Operationen rekonstruieren: h(x) = (π1 ◦ h(x), . . . , πk ◦ h(x)) = h(π1 ◦ h, . . . , πk ◦ hi(x). Diese Funktion ist stetig, da πi ◦ h stetig ist, und h. . .i stetige Funktionen erzeugt. 2 Wir zeigen auch eine analoge Aussage für eine Funktion h : D1 × . . . × Dk → E. Diese Aussage ist etwas schwerer zu zeigen. Dazu benötigen wir ein Lemma über lubs von aufsteigenden arrays. Lemma 1.19 Sei ei,j Elemente einer cpo E, so dass ei,j ≤ ei0 ,j 0 wenn i ≤ i0 und j ≤ j 0 . Dann gilt: {ei,j | i, j ∈ IN} hat eine kleinste obere Schranke und G GG GG G ei,j = ( ei,j ) = ( ei,j ) = ei,i i,j i j j i i TIDS 2, SS13, Kapitel 1, vom 11.4.2013 8 Beweis. Es F ist eine Fleichte Übung, zu zeigen, dass die Mengen {ei,j }, {ei,i }, { j ei,j }, { i ei,j } die gleichen oberen Schranken haben. Zunächst F gilt, dass ei,j ≤ ek,k wenn k = max{i, j}. Sei a eine obere Schranke von { j ei,j }. Dann ist a auch obere Schranke von F {ei,j }. Umgekehrt sei a obere Schranke von {ei,j }. Dann ist für jedes i: j ei,j ≤ a, denn das linke ist eine kleinsteFobere Schranke einer Teilmenge. Damit ist a auch obere Schranke von { j ei,j }. 2 Lemma 1.20 Sei f : D1 × . . . × Dk → E eine Funktion auf cpo’s Di , E. Dann ist f stetig gdw. f stetig in jedem Argument ist. Beweis. “ =⇒ :“ Sei f stetig: Für feste dj definiere die Funktion fi (x) := f (d1 , . . . , di−1 , x, di+1 , . . . , dk ). Diese ist stetig. “⇐:“ Sei der Einfachheit halber k = 2 und sei (x0 , y0 ) ≤ (x1 , y1 ) ≤ . . . eine aufsteigende Kette. Dann gilt: F f (Fi (xi ,F yi )) = f ( i xi , j yj ) lubs für kartesisches Produkt F F = f (xi , j yj ) da f stetig im ersten Argument i F F ( f (xi , yj ) da f stetig im zweiten Argument = Fi j wegen obigem Lemma = i f (xi , yi ) 2 1.4 Funktionenräume Seien D, E cpo’s. Mit [D → E] bezeichnen wir die Menge der stetigen Funktionen D → E. Die Ordnung auf [D → E] sei f ≤ g gdw. ∀d ∈ D : f (d) ≤ g(d). Wenn E ein bottom-Element hat, dann hat [D → E] das kleinste Element ⊥[D→E] , definiert durch: ⊥[D→E] d = ⊥E für alle d ∈ D. Die Ordnung [D → E] ist eine cpo: Betrachte eine Kette f0 ≤ f1 ≤ f2F≤ . . . Für d ∈ D können wir F f0 (d) ≤ f1 (d) ≤ . . . bilden. existiert i fi (d). D.h die Funktion i fi ist so F Deshalb F definiert, dass ( i fi )(d) := F i fi (d). Es bleibt zu zeigen, dass ( i fi ) stetig ist, d.h. ∈ [D → E] ist. Sei d0 ≤ d1 ≤ d2 ≤ . . . eine Kette in D. Dann gilt: F F ( i fi )( j dj ) F F F = ( i fi ( j dj ) nach Definition der Funktion( i fi ) F F = f (d ) da fi stetig ist Fi Fj i j = f (d ) nach dem Lemma über aufsteigende arrays i i j Fj F = j (( i fi )(dj )) Definition von lub für Funktionen TIDS 2, SS13, Kapitel 1, vom 11.4.2013 9 Beispiel 1.21 Wenn I eine Menge mit diskreter Ordnung ist, entspricht der Funktionenraum [I → D] einem Produkt von zu D isomorphen Ordnungen, wobei I die Menge der Indizes ist. Wenn I endlich ist sind das gerade |I|-Tupel. 1.5 Operationen auf Funktionenräumen Anwendung einer Funktion auf das Argument: apply : [D → E] × D → E wobei D, E cpo’s sind. Die Definition ist: apply (f, d) = f (d). Die Funktion apply ist stetig: Sie ist stetig in jedem Argument: 1. stetig im ersten Argument: Sei f0 ≤Ff1 ≤ f2 ≤ eine F Kette von Funktionen. Dann gilt: apply(( i fi ), d) = ( i fi )d F (Definition des lubs von Funktionen) = Fi fi (d) = i apply(fi , d) 2. Stetig im zweiten Argument: Sei d0 ≤ dF 1 ≤ d2 ≤ . . . eine Kette. apply(f, i )) F ( i dF = fF( i di ) = i f (di ) da f stetig = i apply(f, di ) Beispiel 1.22 Currying macht aus einer Funktionen auf Tupeln eine Funktion, die man nacheinander auf die Elemente des Tupels anwenden kann. (Das ist eine Konstruktion, die zeigt, dass man mit einstelligen Funktionen im Prinzip auskommt) curry : [F × D → E] → (F → [D → E]) curry(g) : F → [D → E] curry(g) = λv ∈ F.(λd ∈ D.g(v, d)) Wir argumentieren dass folgendes gilt: 1. curry ist korrekt definiert, d.h. das Bild liegt in (F → [D → E]) 2. curry (g) ist stetig. 3. curryselbst ist stetig Jetzt machen wir das mal von Hand. Man kann zeigen, dass alle diese Konstruktionen automatisch stetig sind. 1. curry(g) ist korrekt definiert: (λd ∈ D.g(v, d)) ist eine stetige Funktion [D → E]. Die Behauptung gilt, denn g ist stetig in jedem Argument. TIDS 2, SS13, Kapitel 1, vom 11.4.2013 10 2. curry(g) ist stetig für alle g: v0 ≤ v1 ≤ F v2 ≤ . . . sei Kette in F . Sei d ∈ DFbeliebig. Dann betrachte curry(g)( i vi )d F = g(( F i vi ), d) = i g(vi , d) = = i curry(g) vi d F F Also stimmt die Funktion curry(g)( i vi ) mit ( i curry(g) vi ) überein. D.h. curry(g) ist stetig. 3. curryist stetig: F Sei g1 ≤ g2 ≤ g3 ≤ F... Dann F gilt: i (curry(gi )) d e = i gi (d, e) = Fi gi (d, e) = ( i gi )(d, F e) da gi ∈ [F × D → E] = curry( i gi ) d e F F D.h. i curry(gi ) und curry( i gi ) stimmen als Funktionen überein. Damit ist curry stetig. 1.6 Lifting Diese Konstruktion addiert ein neues Bottom-Element zu einer cpo. Der Zweck ist i.a., partiell definierte Funktionen handhabbarer zu machen, insbesondere diese auf bekannte Funktionen zurückzuführen. Man kann es auch sehen als Umwandlung einer mathematisch gegebenen Berechnungsmöglichkeit in eine algorithmische, wobei “undefiniert“ und Nicht-terminierung mitbehandelt werden sollen. Die Konstruktion selbst ist einfach: Sei D eine cpo. Definiere D⊥ = D ∪ {⊥}, wobei ⊥ von allen Elementen von D verschieden ist. Die Ordnung auf D⊥ ist die Ordnung von D, zusätzlich gilt ⊥ ≤ d für alle d ∈ D⊥ . Die Einbettung ι : D → D⊥ ist die Funktion mit ι(d) = d. Lemma 1.23 Die Einbettung ist stetig. Sei E eine cpo mit kleinstem Element. Eine Funktion f : D → E kann auf f : D⊥ → E fortgesetzt werden. Diese Funktion nennen wir f ∗ . f (d) wenn d 6= ⊥ f ∗ (d) = ⊥E sonst. d.h. d = ⊥ f ∗ ist stetig: Sei d1 ≤ d2 : wenn d2 = ⊥, dann ist d1 = ⊥. wenn d2 6= ⊥, dann ist f ∗ (d1 ) = f (d1 ) oder f ∗ (d1 ) = ⊥E ≤ f (d2 ). Sei d1 ≤ d2 ≤ . . . eine aufsteigende Kette. Wenn ein di 6= ⊥, dann ist lub f ∗ (di ) = lub f (di ) = f (lub di ) = f ∗ (lub di ) da (lub di ) 6= ⊥. Wenn di = ⊥, dann ist lub f ∗ (di ) = lub ⊥ = ⊥ = f ∗ (lub di ). Bemerkung 1.24 Wenn f durch eine Funktion λx.e beschrieben werden kann, dann schreiben wir statt (λx.e)∗ (d) auch (slet x = d in e) (slet soll “striktes let“ andeuten) ! Das “normale“ (let x = d in e) kann als (d e) übersetzt werden. TIDS 2, SS13, Kapitel 1, vom 11.4.2013 11 Lemma 1.25 Die (Lifting-)Funktion (.)∗ : [D → E] → [D⊥ → E] ist stetig: Beweis. Sei f ≤ g. Sei d ∈ D⊥ . Wenn d = ⊥, dann ist f ∗ ⊥ = ⊥ = g ∗ ⊥. Wenn d 6= ⊥, dann betrachte f d ≤ g d. Wegen f ∗ (d) = f (d) und g ∗ (d) = g(d) ist (.)∗ monoton. Sei f0 ≤ f1 ≤ . . . eine in [D → E] undF d ein Element aus D⊥ . F Kette ∗ Wenn d = ⊥, dann ist ( i fi ) ⊥ = ⊥. Die Kette i fi∗ ⊥ ist konstant = ⊥, also ebenfalls = ⊥. Sei d 6= ⊥. Dann gilt: F F (F i fi )∗ d = F i fi )d (denn so war (.)∗ definiert) = Fi fi (d) = i fi∗ (d) Stetigkeit von fi und Definition von (.)∗ F = ( i fi∗ )(d) Da ( i fi∗ ) punktweise definiert ist 2 Mathematische Operationen auf Mengen können damit “geliftet werden“. Zum Beispiel kann man damit Multiplikation oder andere Funktionen auf Zahlen auch für Argumente verwenden, die möglicherweise undefiniert sind. Die Zahlen werden dann als geliftete Zahlen betrachtet, d.h. als ein flache cpo mit einem kleinstem Element. Diese Methode geht dann davon aus, dass diese neuen Operationen jeweils “undefiniert“ liefern für undefinierte Argumente. D.h. man kann sie gemeinsam mit anderen Funktionen verwenden, die rekursiv definiert und möglicherweise für bestimmte Werte undefiniert sind. geliftete Multiplikation: ∗⊥ := λa, b.(slet x = a, y = b in x ∗ y) Diese Definition liefert auch für das Produkt 0 ∗⊥ ⊥ = ⊥. geliftete Disjunktion (oder) ∨⊥ := λa, b.(slet x = a, y = b in x ∨ y) Man sieht: dieses “oder“ muß erst testen, ob die Argumente definiert sind. Z.B. False ∨⊥ ⊥ = ⊥. D.h. es hat eine andere Semantik als das in Haskell definierte oder (||), das nur das erste Argument immer testet. Für die Untersuchung der Äquivalenzen im ungetypten (lazy) LambdaKalkül wird auch ein Lifting verwendet, das einen Funktionenraum (der bereits ein kleinstes Element hat) nochmals liftet und mit einem extra kleinsten Element versieht. Damit ist es möglich, die Funktionen λx.⊥ und das Element ⊥ zu unterscheiden. Definition 1.26 Eine Funktion f : D → E , wobei D, E cpo’s mit ⊥ sind, ist strikt gdw f ⊥ = ⊥ TIDS 2, SS13, Kapitel 1, vom 11.4.2013 12 Dies ist eine Definition bzgl. Funktionen auf cpo’s. Bei der Semantik von funktionalen Programmiersprachen hat dieser Begriff eine operationale Bedeutung: wenn [[f ]] strikt ist, kann man die Optimierung verwenden, so dass f zuerst sein Argument auswertet, und dann weiter reduziert. Seien D, E cpos mit ⊥. Betrachte die Funktion strict : [D → E] → [D → E], definert durch: f (d) wenn d 6= ⊥ (strict f ) d = ⊥E wenn d = ⊥ Operational bedeutet dies, dass eine Funktion f verändert wird, indem zunächst das Argument ausgewertet wird. Es gilt: 1. (strict f ) ist stetig, wenn f stetig ist. 2. strict ist stetig: Der Beweis geht analog zu (*), zusätzlich sind ein paar Fallunterscheidungen zu machen. 1.7 Summen von cpo’s; Fallunterscheidungen Die Summen-Konstruktion ist die Grundlage der Semantik des caseKonstruktes. Definition 1.27 Seien D1 , . . . , Dk (disjunkte) S cpo’s. Die Summe dieser cpo’s ist einfach die (disjunkte) Vereinigung Di , wobei die Ordnung genau übertragen wird. Die Elemente aus verschiedenen cpo’s sind unvergleichbar. Die Summe schreiben wir auch als D1 + . . . + Dk . Es gibt (injektive) Einbettungen ιi : Di → D1 + . . . + Dk mit ιi (di ) = di Es gilt: Alle Einbettungen sind stetig. (offensichtlich) Oft wird diese Summe explizit mit Konstruktoren konstruiert, da die Di ’s möglicherweise nicht disjunkt sind: Wenn man ci als verschiedene Konstruktoren hat, dann definiert man D1 + . . . + Dk = {ci (di ) | i = 1, . . . , k}. Man kann stetige Funktionen in eine gemeinsame cpo E auch “summieren“: Sei fi : Di → E stetige Funktionen auf cpo’s. Dann definiert man [f1 , . . . , fk ] : D1 + . . . + Dk → E folgendermaßen: [f1 , . . . , fk ]ιi (di ) = fi (di ). D.h. [f1 , . . . , fk ] ◦ ιi = fi . Hat man die Summe mit Konstruktoren ci definiert, dann definiert man: [f1 , . . . , fk ]ci (di ) = fi (di ). Es gilt: Die Operation [., . . . , .] : ([D1 → E] × . . . × [Dk → E]) → ((D1 + . . . + Dk ) → E) ist stetig. Die Summe von cpo’s kann zur Beschreibung des case-Konstruktes dienen. Das einfachste ist “ if“: Die diskrete cpo T = {True, False} kann man als Summe der cpo’s True und False ansehen. 13 TIDS 2, SS13, Kapitel 1, vom 11.4.2013 Seien zwei Funktionen gegeben: λx1 .e1 : True → E λx2 .e2 : False → E Dann kann man definieren: cond0 (t, e1 , e2 ) := [λx1 .e1 , λx2 .e2 ](t) Wenn t = True ergibt, dann erhält man (λx1 .e1 )(True), im anderen Fall (λx2 .e2 )(False). Da man i.a. die Situation hat, dass die Bedingung undefiniert sein kann, betrachtet man E mit ⊥, liftet {True, False} zu {True, False}⊥ , und definiert cond(t, e1 , e2 ) := (slet t = b in cond0 (t, e1 , e2 )) Dies ist das cond mit folgendem Verhalten: wenn t = ⊥, dann ist cond(t, e1 , e2 ) = ⊥, wenn t = True, dann e1 (True) wenn t = False, dann e2 (False) D.h., Die Summenkonstruktion gehört zu einem if. Das kann man ohne Probleme zu einem case verallgemeinern: D1 + . . . + Dk sei Summe von cpo’s, die mit Konstruktoren ci konstruiert worden ist. Wenn Funktionen λxi .ei : Di → E gegeben sind, dann entspricht [λx1 .e1 , . . . , λxn .en ](d) einer Konstruktion mit case: case d of (c1 (x1 ) → e1 (x1 )); ... (cn (xn ) → en (xn )) Im allgemeinen ist das zu schwach, da dies voraussetzt, dass die Auswertung, die den Konstruktor erkennt, auch terminiert. Deshalb nimmt man i.a. die geliftete Summe (D1 +. . .+Dk )⊥ als Summe von cpo’s, die mit Konstruktoren ci konstruiert worden ist. Wenn Funktionen λxi .ei : Di → E gegeben sind, dann entsprechen sich: • (slet x = d in [λx1 .e1 , . . . , λxn .en ](d)) und • case d of 1.8 (c1 (x1 ) → e1 (x1 )); ... cn (xn ) → en (xn ); Stetigkeit von fix Sei D eine cpo mit ⊥. Der kleinste-Fixpunkt-Operator fix : [[D → D] → D] ist definiert als G fix f = f n (⊥) n 14 TIDS 2, SS13, Kapitel 1, vom 11.4.2013 Dies kann man auch schreiben als f ix = G λf.f n (⊥) n Denn λf.⊥ ≤ λf.f (⊥) ≤ . . . ist eine aufsteigende Kette in [[D → D] → D]. Da wir gezeigt haben, dass [[D → D] → D] eine cpo ist, existiert die kleinste obere Schranke, und diese ist auch in [[D → D] → D]. Diese kleinste obere Schranke ist gerade fix: denn G G fix f = f n (⊥) = ( λx.xn (⊥))f n n Also gilt: fix ∈ [[D → D] → D]. 2 Eine Metasprache und Stetigkeit Die Idee einer Metasprache ist eine allgemeine Möglichkeit, Funktionen auf cpos als stetig nachzuweisen, solange sie sich mittels Lambda-Ausdrücken darstellen lassen; und zwar ohne jedesmal einen expliziten Beweis führen zu müssen. Dies soll helfen, für verschiedene Varianten von Lambda-Notationen bzw. Lambda-Kalkülen die Stetigkeit von Ausdrücken aus allgemeineren Prinzipien herzuleiten, anstatt diese jeweils extra nachzurechnen. Damit kann man dann etwas leichter eine denotationale Semantik für eine gegebene Programmiersprache definieren, da man die Stetigkeit aller Ausdrücke nicht gesondert nachweisen muß. Wir nehmen an, dass es cpo’s gibt, so dass wir auch die neu konstruierten cpo’s verwenden dürfen. E ::= Di | E1 × . . . × En | [E1 → E2 ] | [E1 , . . . , En ] | E⊥ 2.1 Syntax für die Lambda-Meta-Sprache Für Ausdrücke: • ci Konstanten für (alle) Elemente in einer festen cpo. • Konstruktoren cE für bestimme cpo’s. Wir nehmen auch an, dass die Summen-cpo’s aus cpo’s gebildet werden, die mit Konstruktoren verkleidet sind. • Funktionen, die fest an eine bestimmte cpo’s gebunden sind: Projektionen, Einbettungen • Generische Konstanten, wie apply, curry, fix, strict. TIDS 2, SS13, Kapitel 1, vom 11.4.2013 15 • e ::= x | λx.e | (e1 e2 ) | (slet x = e1 in e2 ) | (case e of(cE,1 (x1 ) → e1 ); . . . ; (cE,n (xn ) → en )) Analog zum Typcheck für die einfach getypte Lambda-Notation gilt auch hier, dass nur solche Ausdrücke sinnvoll sind, für die man eine sinnvolle Interpretation als Funktion auf einer (konstruierten) cpo herleiten kann. Diese ist analog zum hergeleiteten Typ. Die Bedingungen, die dann gelten müssen, sind: • Variablen haben im Geltungsbereich stets die gleiche Zuordnung • Konstanten haben ihre feste Zuordnung zur cpo, • Jedes Vorkommen einer generische Konstanten hat ebenfalls eine zulässige Belegung. D.h. man kann so tun, als gäbe es für jeden (sinnvollen Typ) eine generische Konstante dieses Typs. • (e1 e2 ): e1 : [D → E], e2 : D und (e1 e2 ) gehört zur cpo E • (slet x = e1 in e2 ): x gehört zur cpo D, e1 zur cpo D⊥ ; e2 und (slet x = e1 in e2 ) gehören zur gleichen cpo E • (case e of(cE,1 (x1 ) → e1 ); . . . ; cE,n (xn ) → en ): Der Ausdruck e gehört zur cpo E, die Variable xi gehört zur Ei , cE,i ist der Konstruktor, der Ei versteckt. E = [E1 , . . . , En ]. Der case-Ausdruck gehört zur cpo E. Wir gehen davon aus, dass sinnvolle Ausdrücke genau diejenigen sind, für die man eine Belegung aller Unterausdrücke mit cpo’s angeben kann, so dass obige Bedingungen erfüllt sind. Dies ist nicht nur zufällig genauso wie im einfach getypten Lambdakalkül. 2.2 Stetigkeit von Funktionen in der Metasprache Hier braucht man wieder die Begriffe wie freie Variablen, gebundene Variablen, die wir hier voraussetzen. Die Konstruktion von syntaktischen Ausdrücken ergibt i.a. Unterausdrücke, die freie Variablen enthalten. Dies muß in die Betrachtungen einfließen. Dazu benötigen wir eine angepaßte Definition der Stetigkeit eines Ausdrucks mit freien Variablen. Definition 2.1 1. Sei e ein Ausdruck, und seien x1 , . . . , xn die freien Variablen in e. Sei x eine Variable. Dann ist e stetig in der Variablen x, wenn für alle zulässigen Belegungen xi : Di mit di ∈ Di für xi 6= x gilt, dass λx.e[di /xi | i = 1, . . . , n, xi 6= x] stetig ist. TIDS 2, SS13, Kapitel 1, vom 11.4.2013 16 2. Ein Ausdruck e ist stetig in seinen Variablen, wenn e stetig in allen freien Variablen ist. Dies ist unabhängig von der Reihenfolge der gewählten Variablen in der Definition, da wir einen Ausdruck e : E als Funktion mit den freien Variablen {x1 : D1 , . . . , xn : Dn } als Funktion von D1 × . . . × Dn → E ansehen können, und bereits gezeigt haben, dass eine Funktion stetig ist, gdw. sie in jedem Argument separat stetig ist. Wir gehen jetzt die Konstrukte einzeln durch und argumentieren, dass alle gebildeten Ausdrücke stetig sind: Variablen x ist stetig in x, denn für jede cpo D, die man x zuordnet, ist λx : D.x als Identität auf D stetig. x ist auch stetig in y für y 6= x, da konstante Funktionen stetig sind. d.h ist stetig in seinen Variablen. Generische Konstanten apply : [[D → E] × D → E], wobei D, E cpo’s sind (polymorph) fix: [D → D] → D curry: (F × D → E) → (F → [D → E]) Diese sind alle stetig, wie bereits gezeigt. Da diese Funktionen keine freien Variablen enthalten, gilt, dass sie stetig in allen Variablen sind. Tupelbildung (e1 , . . . , en ): Wenn alle ei stetig in allen Variablen sind, dann ist (e1 , . . . , en ) ebenfalls stetig in allen Variablen: Sei eine zulässige cpo-Belegung für das Tupel gegeben. Diese ist auch eine für jedes ei . Da alle λx.ei [dy /y | x 6= y] stetig sind, gilt das auch für λx.(e1 , . . . , en )[dy /y|x 6= y] (dies folgt aus den Betrachtungen über Tupelbildung von Funktionen Also ist (e1 , . . . , en ) stetig in seinen Variablen Anwendung Sei e ein Ausdruck. Betrachte zunächst (apply(e1 , e2 )) wobei apply eine generische Konstante ist. Dann gilt: wenn (e1 , e2 ) stetig ist, dann auch (apply (e1 , e2 )). Sei eine Belegung von (apply(e1 , e2 )) gegeben. Dann ist dies auch eine gemeinsame Belegung von apply und (e1 , e2 ). Sei außerdem (e1 , e2 ) stetig in x. Die cpo von (e1 , e2 ) wurde konstruiert als (D → E) × D. Dann ist λx.((e1 , e2 )[dy/y|y 6= x]) stetig und in [F → (D → E) × D] Dann ist auch apply ◦ λx.(e1 , e2 )[dy /y] stetig. Offenbar gilt: apply ◦ λx.(e1 , e2 )[dy /y] = λx.apply(e1 , e2 )[dy /y]. Denn (apply ◦ λx.(e1 , e2 )[dy /y](a) = (apply(e1 , e2 )[dy /y, a/x] = (e1 , e2 )[dy /y, a/x] = (λx.apply(e1 , e2 )[dy /y])(a) Damit ist apply(e1 , e2 ) stetig. Offenbar ist apply(e1 , e2 ) = (e1 e2 ). Also ist Anwendung stetig. 17 TIDS 2, SS13, Kapitel 1, vom 11.4.2013 Abstraktion λz.e ist stetig in allen Variablen Dazu betrachte λx.λz.e[dy /y | y 6= x, z]. Diese Funktion ist stetig, wenn x keine freie Variable in λz.e ist. Wenn x eine freie Variable in λz.e ist, dann ist e[dy /y | y 6= x, z] als Funktion von D1 × D2 → E stetig, wobei x, z die Variablen sind mit x : D1 und z : D2 . Dann können wir diese Funktion transformieren mit curry: curry(λ(x, z).e) = λx.λz.e[dy /y|y 6= x, z]. Da curry als stetig bereits nachgewiesen ist, ist damit die Funktion λx.λz.e[dy /y|y 6= x, z] stetig. Dies gilt für alle x, also ist λz.e stetig in seine Variablen slet: (slet x = e1 in e2 ): Bedingung an eine Belegung mit cpo’s ist : e1 : D⊥ , x : D, e2 : E. e2 ist stetig in allen Variablen, insbesondere ist (λx.e2 ) stetig; damit auch die geliftete Funktion (λx.e2 )∗ . Dies haben wir bereits bei der Betrachtung zu lifting gesehen. Da Anwendung ebenfalls eine Konstruktion ist, die zu stetigen Funktionen führt, gilt: (λx.e2 )∗ (e1 ) ist stetig in allen Variablen. Denn es gilt: (slet x = e1 in e2 ) = (λx.e2 )∗ (e1 ) case: (case e of (cE,1 (x1 ) → e1 ); . . . ; (cE,n (xn ) → en )) Hier muss die Belegung mit cpo’s so sein, wie oben beschrieben: (e gehört zur cpo E, die Variable xi gehört zur cpo Ei , cE,i ist der Konstruktor, der Ei versteckt. E = [E1 , . . . , En ]. Diese Konstruktion ist stetig, da (case e of (cE,1 (x1 ) → e1 ); . . . ; (cE,n (xn ) [λx1 .e1 , . . . , λxn .en ] e definiert ist. Damit sind auch alle (if a then b else c ) stetig. syntaktischen → en )) Konstruktionen als wie Fix : ist offenbar auch stetig, wie oben schon gezeigt. In Sprachen kommt das oft als expliziter Rekursions-Operator vor, manchmal in der Syntax µx.s Zusammenfassend gilt, dass alle mittels der Metasprache bildbaren Ausdrücke stetig sind, insbesondere alle geschlossenen Ausdrücke. 18 TIDS 2, SS13, Kapitel 1, vom 11.4.2013 3 PCF: Programming Language for Computable Functions Diese Sprache wurde von Dana Scott 1969 (LCF) eingeführt, um Grundlagenuntersuchungen an funktionalen Sprachen durchzuführen. PCF ist eine einfach getypte funktionale Sprache, für die wir mit unseren Mitteln eine denotationale Semantik mittels Konstruktionen über cpo’s angeben können. PCF hat als Basisdatentypen natürliche Zahlen und die beiden Wahrheitswerte True, False. Einige wenige Funktionen sind schon vorhanden. 3.1 Syntax Typen: τ ::= num | Bool | τ → τ Ausdrücke: E ::= True | False | 0 | 1 | 2 | ... | pred E | succ E, | zero? E | (if E then E else E ) | x | λx :: τ.E | (E E) | µx :: τ.E Alle zulässigen Ausdrücke sind einfach (monomorph) getypt. D.h. diese haben einen Typ, der keine Variablen enthält. Dieser Typ ist zudem eindeutig. In den Konstrukten λ, µ müssen die Variablen mit einem (monomorphen) Typ versehen sein, damit man den Typ der Ausdrücke angeben kann. Einige Typen von Ausdrücken: 0, 1, 2, . . .: num zero?: num → Bool if−then−else−:: Bool → α → α → α für alle α. D.h. das if-then-else kann man als generisches Konstrukt ansehen, das für jeden Typ τ zu einem extra if-then-elseτ instanziiert wird. Der neue Operator µ ist der Fixpunktoperator. Typregel für µ ist: λx.E : τ → τ (µx : τ.E) : τ 3.2 Operationale Semantik von PCF Die operationale Semantik wird zunächst als “big-step“ Semantik angegeben. Mit v bezeichnen wir einen Wert und mit w einen Wert, oder einen λoder µ-Ausdruck. Die Regeln für die Reduktion →op sind: 19 TIDS 2, SS13, Kapitel 1, vom 11.4.2013 e1 →op True; e2 →op v if e1 then e2 else e3 →op v e1 →op False; e3 →op v if e1 then e2 else e3 →op v s →op (λx.e) e[a/x] →op w (λx.e) a →op w (e[(µx.e)/x]) →op w (µx.e) →op w t →op pred; e →op n und n > 0 t e →op (n − 1) t →op succ; e →op n t e →op (n + 1) t →op pred; e →op 0 t e →op 0 t →op zero?; e →op 0 t e →op True t →op zero?; e →op n und n 6= 0 t e →op False Als Reduktionsrelation (d.h. “small-step Semantik“) kann man das auch folgendermaßen definieren: if True then e2 else e3 if False then e2 else e3 pred n pred 0 succ n zero? 0 zero? n (λx.e)a (µx.e) → → → → → → → → → e2 e3 n−1 falls n > 0 0 n+1 True False falls n > 0 e[a/x] (e[µx.e/x]) Betrachten wir noch Reduktionskontexte in PCF: R ::= [] | R E | if R then E else E | pred R | succ R | zero? R , und erlauben nur Reduktionen, die in einem Reduktionskontext stattfinden: d.h. nur R[s] → R[t] ist zulässig. Diese Reduktion im Reduktionskontext bezeichnen wir auch als Normalordnungsreduktion. Die transitive Hülle ∗ von → bezeichnen wir als → −. ∗ Es gilt, dass →op ⊆ → −. Die Sprache PCF ist zwar einfach und monomorph getypt, aber es ist leicht zu sehen, dass sie Turing-mächtig ist. D.h. man kann alle berechenbaren Funktionen definieren. Definition 3.1 Sei t ein PCF-Ausdruck. • t ist in Normalform gdw. t nicht mehr reduzierbar ist. 20 TIDS 2, SS13, Kapitel 1, vom 11.4.2013 • t ist in WHNF (schwache Kopfnormalform): gdw. t ≡ λx.t0 oder t ist ein Basiswert vom Typ num oder Bool. Definition 3.2 Sei t ein geschlossener PCF-Ausdruck. Wenn ein t0 exi∗ stiert mit t → − t0 , wobei t0 eine WHNF ist, dann schreiben wir t0 ↓. 3.3 Übersetzung PCF nach Haskell Bevor wir die Eigenschaften von PCF genauer betrachten, geben wir zunächst eine Übersetzung nach Haskell an. Wir wollen diese Übersetzung nicht formal verifizieren. Diese Übersetzung soll nicht dazu dienen, PCF semantisch zu fundieren, sondern soll illustrieren, wie man für PCF schnell einen Interpreter bauen kann. Außerdem kann man danach die Ausführung von PCF-Ausdrücken und die Unterschiede zu Haskell schneller sehen. Die übersetzten Funktionen sind zum Teil allgemeiner, d.h. in Haskell haben sie einen polymorphen Typ statt eines monomorphen in PCF; sie könnten Argumente zulassen, die in PCF verboten wären. Aber es soll folgendes erfüllt sein: 1. Übersetzte Ausdrücke sind wohlgetypt in Haskell. ∗ 2. Sei t ein Ausdruck in PCF und t → − n und n eine Zahl oder ein Boolescher Wert. Wenn wir die Übersetzung τ und die Reduktion in Haskell ebenfalls mit Index H andeuten, dann gilt für die Übersetzung: ∗ tH → − H nH . 3.3.1 Übersetzung nach Haskell: Typen: τ ::= num | Bool | τ → τ Ausdrücke: Boolesche Werte und Zahlen kann man direkt übersetzen. if-then-else und pred, succ ebenfalls direkt. zero?H := iszero (λx : τ.E)H := \x → EH (µx : τ.E)H = fix(\x → EH ) Folgende Haskell-Definition werden für diese Übersetzung benötigt: pred n succ n iszero x fix f = = = = if n > 0 then n-1 else 0 n+1 x == 0 f (fix f) Man kann alternativ auch das let(rec)-Konstrukt zur Übersetzung des µ verwenden: (µx : τ.E)H = let x = EH in x Die Fixpunktbildung entspricht offenbar der (einfachen) Rekursion. 21 TIDS 2, SS13, Kapitel 1, vom 11.4.2013 Beispiel 3.3 Addition mittels succ. Ein Versuch in funktionaler Notation: add s t := if zero?(s) then t else succ (add (pred s) t) In PCF sieht diese Funktion so aus: add := µa.(λs, t.if zero? (s) then t else succ (a(pred s) t) Die Haskell-Übersetzung dieser Funktion ist: 1. erste Variante add = fix (\a -> (\s t -> if iszero s then t else succ (a (pred s) t))) 2. Zweite Variante add = let a = in a (\s t -> if iszero s then t else succ (a (pred s) t)) Beispiel 3.4 Multiplikation: funktional: mult s t := if zero?(s) then 0 else add t (mult (pred s) t) in PCF: mult := µm.λs, t.if zero?s then 0 else add t (m(pred s)t) Haskell-Übersetzung: 1. mult = fix (\m -> (\s t -> if iszero s then 0 else add t (m (pred s) t))) 2. mult = let m = (\s t -> if iszero s then 0 else add t (m (pred s) t)) in m 4 Eigenschaften von PCF Definition 4.1 Eine Relation → ist konfluent (bzw. Church-Rosser) gdw. ∗ ∗ für alle s, s1 , s2 : wenn s → − s1 und s → − s2 , dann existiert ein weiterer Term ∗ ∗ s3 , so dass s1 → − s3 und s2 → − s3 . Satz 4.2 Die Reduktionsrelation → ist konfluent. TIDS 2, SS13, Kapitel 1, vom 11.4.2013 22 Beweis. Mit Methoden wie in (Barendregt) 2 Ohne den Fixpunktoperator ist diese Sprache nicht Turingäquivalent: Es gilt, dass ohne Fixpunktoperator alle Reduktionen terminieren. Diese Aussage benötigen wir auch später für die Adäquatheit der denotationalen Semantik. Folgende Aussage entspricht einer Variante des Progress-Lemmas. Lemma 4.3 In PCF gilt: geschlossene Terme, die Normalformen vom Typ Bool bzw. num sind, können nur die Basiswerte selbst sein, d.h. Zahlen oder Wahrheitswerte. Beweis. Wir zeigen dies mit Induktion nach der Größe der Terme: Es gilt: Ein Term M in Normalform kann kein µ-Ausdruck enthalten, denn jeder µ-Term ist reduzierbar, also nicht in Normalform. Wir gehen die Konstruktionsmöglichkeiten durch. • M ≡ 0, n, True, False: trivial. • M ≡ pred M 0 , succ M 0 , zero? M 0 : Das gilt mit Induktion, da M 0 ebenfalls von Basistyp, und eine Ein-Schritt Reduktion ausreicht, um den Ausdruck ebenfalls in einen Basiswert zu überführen. • M ≡ if M0 then M1 else M2 : wenn dieser Term in Normalform ist, dann ist auch M0 in NF, also True oder False. Dann kann man aber reduzieren. • M ≡ λx.M 0 : kann nicht sein, da dies vom falschen Typ ist. • M ≡ (M1 M2 ): Dieser Term muß von der Form 0 ((. . . (Mn Mn−10 ) . . . ..)M2 ) sein. Der Term Mn0 kann nur eine Abstraktion sein. If-then-else ist nicht möglich, da wegen der Induktionshypothese ein inneres if-then-else nicht in NF sein kann. Damit kann man aber β-reduzieren, und der Term ist nicht in Normalform. 2 Wir betrachten jetzt das Fragment P CF−µ : das ist die Sprache PCF ohne den µ-Operator. Definition 4.4 Definiere eine Menge Ttrm von P CF−µ -Termen als kleinste Menge, die folgendes erfüllt: 1. Wenn M geschlossen ist und vom Typ num oder Bool, und alle Reduktionen, die von M ausgehen, terminieren, dann ist M ∈ Ttrm . 2. Sei M : τ1 → τ2 . Dann ist M ∈ Ttrm , wenn für alle geschlossenen N : τ1 ∈ Ttrm gilt: (M N ) ∈ Ttrm . TIDS 2, SS13, Kapitel 1, vom 11.4.2013 23 Jetzt definiere noch die Menge Ttrm,v folgendermaßen: Wenn M die freien Variablen x1 , . . . , xn hat, dann ist M ∈ Ttrm,v , wenn σ(M ) ∈ Ttrm für alle σ = {xi → Ni | i = 1, . . . , n} wobei Ni ∈ Ttrm . Offenbar gilt: 1. Sei M ∈ Ttrm . Dann terminieren alle von M ausgehenden Reduktionen. 2. M ∈ Ttrm,v gdw. für alle σ, die Ttrm - Terme für freie Variablen einsetzen und für alle geschlossenen N1 . . . Nk ∈ Ttrm , so dass σ(M ) N1 . . . Nk von Basistyp ist : σ(M ) N1 . . . Nk hat nur terminierende Reduktionen. Satz 4.5 Betrachte das Fragment P CF−µ der Sprache PCF. Für die Ausdrücke dieser Sprache gilt, dass sie in Ttrm,v enthalten sind. D.h. Man kann jeden geschlossenen Term in P CF−µ nur endlich oft reduzieren. Beweis. Mit Induktion nach der Größe der Terme. • M ≡ x: trivial • M ≡ 0, True, False, n: trivial, da alles in Normalform. • M ≡ pred M 0 , succ M 0 , zero?(M 0 ): Betrachte σ(M 0 ). Nach Induktion terminiert σ(M 0 ). Wegen der operationalen Definitionen von succ, pred, zero? gilt dies auch für σ(M ). Also ist M ∈ Ttrm,v . • M ≡ if M0 then M1 else M2 Nehme an, dass Ni ∈ Ttrm sind und σ Substitution, die nur Terme aus Ttrm einsetzt, so dass (σM ) N1 . . . Nn geschlossener Term vom Basistyp ist. Induktion zeigt, dass (σ M0 ) terminierend ist, ebenso (σM1 ) N1 . . . Nn und (σM2 )N1 . . . Nn also ist auch (σM ) N N1 . . . Nn terminierend. Dies gilt für alle terminierenden, geschlossenen Ni . Also gilt M ∈ Ttrm,v . • M ≡ (L N ): Da L, N ∈ Ttrm,v nach Induktionshypothese, gilt für alle Substitutionen σ die nur Terme aus Ttrm einsetzen, dass auch σL, σN ∈ Ttrm . Wegen der Definition von Ttrm ist auch (σL (σN )) terminierend, also ist (L N ) ∈ Ttrm,v . • M = λx.M 0 : Zu zeigen ist: (λx.σM 0 )N ∈ Ttrm ist terminierend für geschlossene N ∈ Ttrm,v und Substitutionen σ, die nur Terme aus Ttrm einsetzen. Da M 0 ∈ Ttrm,v nach Induktionshypothese, gilt s[N/x]M 0 ∈ Ttrm . Also gilt das auch für σ(M 0 [N/x]). Damit ist M = λx.M 0 ∈ Ttrm,v . TIDS 2, SS13, Kapitel 1, vom 11.4.2013 24 2 Korollar 4.6 1. Alle Terme des µ-freien PCF-Fragments haben nur terminierende Reduktionen. 2. Alle Terme vom Basistyp im µ-freien PCF-Fragment reduzieren zu einer Zahl, oder True, False. Korollar 4.7 Der µ-Operator ist nicht im µ-freien PCF-Fragment definierbar. D.h dies ist eine echte Erweiterung. 5 Denotationale Semantik von PCF Für jedes Konstrukt müssen wir angeben, wie syntaktische Objekte denotiert werden: Da PCF-Unterterme freie Variablen enthalten können, benötigen wir eine Umgebungsvariable. Diese Umgebung ist eine partielle Funktion von Variablen in zugehörige Werte. Wir nehmen an, dass Variablen mit einem Typ markiert sind. Wir nehmen auch an, dass die Ausdrücke entsprechend dem einfach getypten Lambda-Kalkül getypt sind. Als Domains verwenden wir cpo’s, die wir im Vorlesungsteil zu cpo’s eingeführt und ausführlich behandelt haben. Die verwendeten Notationen sind dort erklärt. Die Denotationen der Typen ergeben sich rekursiv folgendermaßen: [[num]] = IN⊥ geliftete natürliche Zahlen [[bool]] = {True, False}⊥ geliftete zweielementige Menge [[τ1 → τ2 ]] = [[[τ1 ]] → [[τ2 ]]]⊥ stetige Funktionen, geliftet Wir benutzen hier geliftete Funktionenräume, d.h. es gibt einen extra hinzugefügten Wert ⊥, der verschieden ist von der Funktion die stets ⊥ liefert. Der Funktionenraum vor dem Liften hat bereits ein kleinstes Element: es ist die Denotation von λx.⊥. Die Anwendung von ⊥ als Funktion auf ein Argument ergibt in D stets ebenfalls ⊥. Das gleiche gilt für die Denotation von λx.⊥, d.h. die Extensionalität gilt nicht mehr in D: es gibt verschiedene Objekte, die als Funktionen gleich sind. Beachte, dass in anderen Artikeln auch stetige Funktionenräume benutzt werden ohne Lifting. Cartesische Produkte werden nicht benötigt, da es keine Datenkonstruktoren, insbesondere keine Tupel in PCF gibt. Diese kann man verwenden, wenn man PCF mit Tupeln erweitern will. Die zugehörigen Denotationen für geschlossene Terme M :: τ sind dann jeweils aus der Denotation [[τ ]] des Typs τ , d.h. [[M ]] ∈ [[τ ]]. Bei offenen 25 TIDS 2, SS13, Kapitel 1, vom 11.4.2013 Termen gilt das ganz ähnlich, nur muß man noch Einsetzungen für die freien Variablen hinzufügen. Im folgenden lassen wir die Typisierungsbezeichnungen teilweise weg. Trotzdem nehmen wir an, dass diese vorhanden sind. Definition 5.1 (denotationale Semantik von PCF) ρ ist eine Umgebung, die Variablen Werte aus der richtigen Menge zuordnet. ρ(x :: τ ) ∈ [[τ ]] Diese Umgebung wird benötigt, damit man Unterausdrücke, die freie Variable enthalten, behandeln kann. Ausdrücke ohne freie Variablen (geschlossene Ausdrücke) haben eine denotationale Semantik, die unabhängig von einer Umgebung ist. Die Vorstellung ist: Für alle globalen Werte (die in ρ) gilt die Definition bereits. Wir benutzen λ auch auf der Seite der Denotationen, aber notieren es dort als λ [[x]] ρ [[n]] ρ [[pred M ]] ρ = = = = = [[succ M ]] ρ = = [[if M then L1 else L2 ]] ρ = = = [[(M N )]] ρ = [[λx.M ]] ρ = [[µx.M ]] ρ = ρ(x) n ([[M ]] ρ) − 1 0 ⊥ ([[M ]] ρ) + 1 ⊥ ⊥ [[L1 ]] ρ [[L2 ]] ρ ([[M ]] ρ) ([[N ]] ρ) λy.([[M ]](ρ[y/x])) fix(λy.([[M ]](ρ[y/x])) (hier müssen M und x wenn wenn sonst wenn wenn wenn wenn wenn ([[M ]] ρ) > 0 ([[M ]] ρ) = 0 ([[M ]] ρ) ≥ 0 ([[M ]] ρ) = ⊥ [[M ]] ρ = ⊥ [[M ]] ρ = True [[M ]] ρ = False gleichen Typ haben) Um zu sehen, dass die Definitionen alle stetig sind, wenden wir die Überlegungen zur Meta-Notation an. Die einfache Typisierung aller Ausdrücke stellt sicher, dass es jeweils zulässige Belegungen mit cpo’s gibt. • Alle Konstrukte sind stetig: succ, pred und zero? sind auf gelifteten, diskreten cpo’s definiert • Applikation, Abstraktion, Fixpunktbildung sind ebenfalls stetig als generische Konstanten. • if-then-else ist als case - Konstrukt über der gelifteten Summe ebenfalls stetig. ∗ Satz 5.2 Seien M und N PCF-Ausdrücke. Wenn M → − N , dann gilt: [[M ]] ρ = [[N ]] ρ für alle ρ . TIDS 2, SS13, Kapitel 1, vom 11.4.2013 26 Beweis. Wir zeigen dies für die Einschrittrelation →. Dann gilt das auch für die transitive Hülle. Wir betrachten dabei jeweils den Unterausdruck, der unmittelbar reduziert wird (den Redex). • pred n → n − 1: [[pred n]] ρ = ([[n]] ρ) − 1 = n − 1. Analog für pred 0, succ n, zero? n. • (if True then L1 else L2 ) → L1 : [[if True then L1 else L2 ]] ρ = [[L1 ]] ρ für alle ρ. • Analog: False-Fall • (λx.M )N → M [N/x]: [[(λx.M )N ]] ρ = ([[(λx.M )]] ρ)([[N ]] ρ) = (λy.[[M ]] ρ[x → y])([[N ]] ρ) = [[M ]] ρ[x → ([[N ]] ρ)] = [[M [N/x]]] ρ( Induktion nach der syntaktischen Struktur von M ) • (µx.M ) → (M [(µx.M )/x]; = = = = = [[(µx.M )]] ρ fix(λy.([[M ]](ρ[x → y]))) (λy.([[M ]](ρ[x → y]))(f ix(λy.([[M ]](ρ[x → y]))) (λy.([[M ]](ρ[x → y])))([[(µx.M )]] ρ) ([[M ]](ρ[x → ([[(µx.M )]] ρ)]))) [[(M [(µx.M )/x])]] ρ 2 Die andere Richtung, nämlich dass der semantische Wert etwas über die Reduktion aussagt, ist etwas komplizierter. Die gewünschte Aussage für geschlossene Ausdrücke M, N ist: Wenn [[M ]] ∅ = [[N ]] ∅ und N eine WHNF ∗ ist, dann auch M → − N . Dies ist zuviel verlangt, es gilt für Konstanten wie Zahlen und die Booleschen Konstanten, aber nicht für Abstraktionen, denn Lambda-abstraktionen in WHNF sind nicht notwendig auch in Normalform. Beachte, dass durch das Liften der Funktioneräume [[λx.⊥]] 6= [[⊥]], was dazu passt, dass λx.⊥ eine WHNF hat, während ⊥ keine WHNF hat. Das folgende werden wir nachweisen: ∗ Wenn [[M ]] ∅ = m ist, dann gilt M → − m. Zunächst zeigen wir, wie wir alle Fixpunktausdrücke approximieren können. Sei M = µx.M 0 ein µ- Ausdruck, dann definiere: 27 TIDS 2, SS13, Kapitel 1, vom 11.4.2013 Semantische Seite: Betrachte d = fix(λy.([[M 0 ]](ρ[x → y])) Offenbar ist dies ein lub der Kette dn := en ⊥ wobei e = (λy.([[M 0 ]](ρ[x → y])) Syntaktische Seite: M0 := µx.x M k+1 := (λx.M 0 )Mk Lemma 5.3 Es gilt: [[Mk ]] ρ = dk Beweis. Induktion nach k: [[M0 ]] ρ = ⊥ = d0 . Angenommen, es gilt bereits für k. [[M k+1 ]] ρ = [[(λx.M 0 )M k ]] ρ = ([[(λx.M 0 )]] ρ)([[M k ]] ρ) = ([[(λx.M 0 )]] ρ)dk = λy.([[M 0 ]](ρ[x → y]))dk = e(ek ⊥) = ek+1 ⊥ = dk+1 . 2 Lemma 5.4 Sei N ein geschlossener PCF-Term, so dass [[N ]] = n eine Zahl ∗ ist und alle µ-Ausdrücke in N von der Form µx.x sind. Dann gilt N → − n. Beweis. Angenommen, das ist falsch. Dann gibt es einen Term N :: num mit [[N ]] = n, alle µ-Ausdrücke in N sind von der Form µx.x und die Normalordnungsreduktion für N terminiert nicht. Da alle Reduktionen im µ-freien Fragment von PCF terminieren, muß die Normalordnungsreduktion irgendwann einen der Unterterme µx.x reduzieren. Wir nehmen einen Term, für den das gilt, und der eine kleinste Anzahl von Normalordnungsreduktion benötigt, bis das erste mal µx.x reduziert wird. Unter diesen wählen wir den kleinsten Term. Offenbar können wir annehmen, dass die erste Normalordnungsreduktion bereits einen Term µx.x reduziert. Da der Term µx.x ein Normalordnungsredex ist, kann er nur in folgendem Kontext auftauchen: pred(µx.x), succ(µx.x), zero?(µx.x), if (µx.x) then e1 else e2 , ((µx.x)e). In jedem dieser Fälle können wir den Term verkleinern, indem wir den Redex durch µx.x ersetzen. Die denotationale Semantik des Terms bleibt gleich, da pred ⊥, succ ⊥, zero? ⊥, if ⊥ then e1 else e2 , (⊥ x) jeweils als Denotation wieder ⊥ haben. Die operationale Semantik bleibt ebenfalls gleich, d.h. die Normalordnungsreduktion terminiert auch für den neu konstruierten Term nicht. Der konstruierte Term ist jedoch kleiner geworden. D.h. der Term selbst muß µx.x sein. Dessen Denotation ist aber ⊥, im Widerspruch zur Annahme. 2 Satz 5.5 Sei N ein geschlossener PCF-Term, so dass [[N ]] = n eine Zahl ∗ ist. Dann gilt N → − n. Beweis. Induktion nach der Anzahl der echten µ-Ausdrücke in N , wobei wir die Ausdrücke µx.x nicht mitzählen. 28 TIDS 2, SS13, Kapitel 1, vom 11.4.2013 Basis. Wenn N keine echten µ-Ausdrücke enthält, dann terminiert die Reduktion von N mit einem n0 nach obigem Lemma. Wir haben bereits gezeigt, dass dann n = n0 sein muß. Induktionsschritt. Wähle in N einen innersten echten µ-Ausdruck M aus, der selbst keine echten µ-Ausdrücke mehr enthält und definiere Nk als die Terme, die entstehen, wenn für N jeweils M k (wie oben) eingesetzt wird. F Wir übernehmen dieFBezeichnungen wie oben. Da k dk = d (für alle ρ) ist, gilt somit auch k [[Nk ]] = [[N ]] = n. Hierbei können wir F die Stetigkeit verwenden, die wir für die Metasprache gezeigt haben. Da k [[Nk ]] = [[N ]] = n, muß die Folge [[Nk ]] für k = 1, 2, . . . folgende Form haben: ⊥, ⊥, . . . , ⊥, n, n . . . . Also gibt es ein k0 , so dass [[Nk0 ]] = [[N ]] = n. Nk0 hat einen µ-Ausdruck weniger als N , dann gilt die Induktionshypothese, ∗ und wir können schließen, dass Nk0 → − n. Somit gibt es auch eine Normal∗ ordnungsreduktion Nk0 → − n. Da der Term M k0 im Innern einen nichtterminierenden Term enthält, wurde für diesen die WHNF nicht berechnet. Damit gilt: Man kann den inneren Term ⊥ wieder durch M ersetzen und ∗ erhält Nk00 mit Nk00 → − n. Da aber Nk00 durch Reduktion aus N hergeleitet ∗ ∗ ∗ werden kann, d.h. N → − Nk00 → − n, gilt insgesamt N → − n. 2 Korollar 5.6 Sei N ein geschlossener PCF-Term vom Typ num. Dann gilt [[N ]] = ⊥ gdw. N keine terminierende Reduktion hat. Was hier noch zu zeigen ist, wäre die Aussage: Wenn N geschlossen ist und [[N ]] 6= ⊥, dann gilt N ↓ 6 Volle Abstraktion und Gleichheit Gleichheit von Funktionen kann man jetzt auf zwei Arten definieren: • Die denotationale Gleichheit: s =d t gdw. [[s]] = [[t]] In dieser Semantik gilt: 29 TIDS 2, SS13, Kapitel 1, vom 11.4.2013 die Funktion por (paralleles “or“) mit por True True False False ⊥ True False ⊥ ⊥ True False True False True ⊥ ⊥ False ⊥ = = = = = = = = = True True True False True True ⊥ ⊥ ⊥ ist monoton und stetig, also im Modell enthalten, aber es gibt keinen PCF-Ausdruck, der die Denotation von por hat. • kontextuelle Gleichheit. Ein Programmkontext ist ein Ausdruck C mit einem Loch an einer Stelle, an der ein Unterterm stehen darf. Wir schreiben dies als C[]. Wir definieren s =c t gdw. für alle Programmkontexte C[] gilt: C[s]↓ gdw. C[t]↓. Diese kontextuelle Gleichheit vergleicht Terme anhand ihres Terminierungsverhaltens in allen Programmen. D.h. wenn sich s, t nicht unterscheiden lassen durch Einsetzen in einen Programmkontext, dann werden sie als gleich betrachtet. Aussage 6.1 Es gilt s =d t =⇒ s =c t (Adäquatheit der denotationalen Semantik) Beweis. Wenn s =d t, dann gilt für alle Programmkontexte C[] auch C[s] =d C[t]. Wenn [[C[s]]] nicht ⊥ ist, dann ist auch [[C[t]]] nicht ⊥. Dann gilt für beide Terme C[s] und C[t], dass diese konvergieren. Damit ist s =c t. 2 Übungsaufgabe 6.2 Definiere in PCF die logischen Funktionen wie not, and, or. Lemma 6.3 In PCF gilt für alle Funktionen f : τ1 → . . . τn → B (Booloder num). Die Funktion f ist entweder konstant oder strikt in einem Argument. Beweis. Annahme: Die Funktion f sei nicht konstant. Dann betrachte eine Reduktion in Normal-Ordnung wobei der Term (f t1 . . . tn ) reduziert wird. Wenn die Unterausdrücke ti nicht reduziert werden, dann wäre f konstant, also wird mindestens ein Term ti zu WHNF reduziert. Da der Index i des 30 TIDS 2, SS13, Kapitel 1, vom 11.4.2013 reduzierten ti unabhängig vom Ausdruck ti ist, gibt es einen Index i, so dass ti reduziert wird. Die Funktion f ist damit strikt im i-ten Argument. 2 Wir betrachten die Funktion F = λxλf. if(and (f True ⊥) (f ⊥ True) not (f False False)) then x else True Lemma 6.4 Es gilt (F True) =c (F False) Beweis. Wir nutzen das obige Lemma aus. Wir wenden (F True) und (F False) auf eine Funktion f vom Typ Bool → Bool → Bool an. Wenn diese konstant ist, dann kann der Ausdruck (and (f True ⊥) (f ⊥ True) (not (f False False))) nicht zu True auswerten. Damit stimmen (F True f ) und (F False f ) überein. Wenn f nicht konstant ist, dann ist f strikt im ersten oder im zweiten Argument, und das Ergebnis ist jeweils ⊥. 2 Wenn wir die beiden Funktionen (denotational) semantisch betrachten, dann gilt: Lemma 6.5 Es gilt (F True) 6=d (F False) Beweis. Wende die Denotationen auf por an. Dann gilt: (F True por) = True und (F False por) = False. 2 Definition 6.6 Eine denotationale Semantik ist voll abstrakt, wenn die kontextuelle Gleichheit mit der denotationalen Gleichheit übereinstimmt. Korollar 6.7 Unsere denotationale Semantik von PCF ist nicht voll abstrakt. Begründung: Die beiden Ausdrücke (F True) und (F False) sind ein Gegenbeispiel, denn (F True) =c (F False), aber (F True) 6=d (F False). Abhilfe: Alternative 1: Um eine voll abstrakte denotationale Semantik zu erhalten, fügt man in der Syntax parallele Operationen hinzu: Es gilt, dass PCF + por als voll abstrakte Semantik genau die bisherige denotationale Semantik hat. Allerdings sagt diese Semantik dann nichts mehr aus über Eigenschaften sequentieller Programme. 31 TIDS 2, SS13, Kapitel 1, vom 11.4.2013 Alternative 2 Man verkleinert die Grundmenge, die der denotationalen Semantik zur Verfügung steht. Dies ergibt eine denotationale Semantik, die auf der operationalen basiert, und die aus allen Ausdrücken erzeugt wird. Problem hierbei: Gibt es ein effektives Konstruktionsverfahren für diese Grundmenge? Leider gelten selbst bei endlichem Grundbereich negative Aussagen: • Es gibt kein effektives Konstruktionsverfahren, wenn man nur Bool als Grundmenge nimmt? Das “Lambda-Definierbarkeits-Problem für endliches PCF ist unentscheidbar“ • Die kontextuelle Gleichheit ist unentscheidbar, auch wenn man nur [[Bool]] als Grundmenge nimmt. D.h. man kann von zwei gegebenen Ausdrücken s, t in PCF, die keinen Bezug zu Zahlen haben, d.h. nur Boolesche Konstanten und Typen, aber natürlich auch Funktionstypen verwenden, nicht algorithmisch entscheiden, ob diese kontextuell gleich sind. 7 Satz von Bekić Man sieht: In PCF fehlen Datenkonstruktoren (Produkttypen, semantisch: Kreuzprodukte.) Programmiertechnisch bewirkt dies unter anderem, dass wir keine Datenstrukturen auf einfache Weise mit Konstruktoren aufbauen können: keine Paare, Listen u.ä. Außerdem scheint die einfache Möglichkeit zu fehlen, verschränkt rekursive Funktionen zu definieren. Wir gehen nochmal zurück zu cpo’s und wollen jetzt zeigen, dass es diese Möglichkeit doch gibt, allerdings ist diese etwas versteckt. Sei D cpo mit ⊥ und F : D → D eine stetige Funktion. Dann existiert ein kleinster Fixpunkt fix(F ). Für diesen gilt: (fix1) F (d) ≤ d =⇒ fix(F ) ≤ d von F ) (fix(F ) ist auch kleinster Präfixpunkt (fix2) F (fix(F )) = fix(F ) Der Satz von Bekić sagt etwas aus über die Existenz von Fixpunkten auf Kreuzprodukten und wie diese aus koordinatenweisen Fixpunkten berechnet werden können. Satz 7.1 (Bekić) Seien D, E cpo’s mit ⊥ und F : D × E → D und G : D × E → D stetige Funktionen. Der kleinste Fixpunkt von hF, Gi : D × E → D × E läßt sich darstellen als hfˆ, ĝi wobei 32 TIDS 2, SS13, Kapitel 1, vom 11.4.2013 fˆ = µf.F (f, µg.G(µf.F (f, g), g)) (= µf.F (f, ĝ)) ĝ = µg.G(µf.F (f, g), g) Beweis. 1. Ist Fixpunkt: fˆ ist Fixpunkt von µf.F (f, ĝ) , also ist fˆ = F (fˆ, ĝ). ĝ = G(µf.F (f, ĝ), ĝ) = G(fˆ, ĝ). Damit ist hF, Gi(fˆ, ĝ) = (fˆ, ĝ). 2. Seien (f0 , g0 ) kleinere Fixpunkt von hF, Gi. Dann gilt f0 ≤ fˆ und g0 ≤ ĝ. Wir zeigen die umgekehrten Ungleichungen. Da f0 = F (f0 , g0 ), gilt auch µf.F (f, g0 ) ≤ f0 . G ist monoton, also gilt: G(µf.F (f, g0 ), g0 ) ≤ G(f0 , g0 ) = g0 Also gilt ĝ ≤ g0 . Denn ĝ ist der kleinste Präfix-Punkt von λg.G(µf.F (f, g), g). Monotonie von F zeigt jetzt, dass : F (f0 , ĝ) ≤ F (f0 , g0 ) = f0 . Damit ist f0 ein Präfixpunkt von λf.F (f0 , ĝ). Also ist fˆ ≤ f0 . 2 Da wir den Satz auch für schwächere Strukturen (partielle Ordnungen, aber keine cpo’s) benutzen wollen, formulieren wir den Satz mit schwächeren Voraussetzungen: Satz 7.2 (Bekić) (Version 2) Seien D, E partielle Ordnungen mit ⊥ und F : D ×E → D und G : D ×E → D monotone Funktionen. Wir nehmen an, dass die kleinsten Fixpunkte in folgenden Formeln existieren. Der kleinste Fixpunkt von hF, Gi : D × E → D × E läßt sich darstellen als hfˆ, ĝi wobei fˆ = µf.F (f, mg.G(µf.F (f, g), g)) (= µf.F (f, ĝ)) ĝ = µg.G(µf.F (f, g), g) Es gibt auch eine symmetrische Form dieses Satzes: fˆ = µf.F (f, µg.G(f, g)) ĝ = µg.G(µf.F (f, g), g) Offenbar ist es kein Problem, den Satz von Bekić auch für mehrere Variablen zu formulieren und zu zeigen. In der Anwendung haben wir meist F : D → D → D und G : D → D → D. Das erste entspricht einer rekursiv definierten Funktion f , das zweite Argument der rekursiv definierten Funktion g. Ein Programmkonstrukt, das sich auf diesen Satz stützen kann, ist das letrec: TIDS 2, SS13, Kapitel 1, vom 11.4.2013 33 (letrec x = s, y = t in e) x, y dürfen in s, t und e vorkommen und sind dort durch das letrec gebunden. Die Anwendung ergibt dann folgende Semantik: ŷ = µy.t[(µx.s)/x] x̂ = µx.s[ŷ/y] Somit könnten wir mittels Bekić’s Satz die Sprache PCF auch um ein letrec mit mehreren Variablen erweitern. Der Satz von Bekić zeigt: diese Erweiterung fügt keine neue Ausdruckskraft hinzu: 8 Einfach getypte Kombinatorsprache: P CFK Da Haskell im Prinzip auch ohne die Konstrukte λ, µ, letrec auskommen kann, ist es interessant, die Beziehung von PCF zu einer Kombinatorvariante von PCF zu untersuchen. Wir nennen dies funktionale Sprache P CFK . Diese hat die gleichen Typen wie PCF. Ebenso die gleichen Basiskonstanten und Funktionen. Es gibt aber kein λ und kein µ. Stattdessen gibt es rekursive Definitionen von Kombinatoren. Den Kombinatoren muß ein monomorpher Typ zugewiesen werden. Definitionen sind von der Form: (hnamei : hT ypi) x1 . . . xn = e wobei e andere Kombinatornamen enthalten darf. Alle freien Variablen von e sind in {x1 , . . . , xn } enthalten. main: ist der (reservierte) Name des auszuwertenden Ausdrucks. Es gilt: Diese Sprache P CFK ist äquivalent zu PCF. Den Begriff der Äquivalenz müssen wir genauer spezifizieren: Es gibt eine natürliche Übersetzung von PCF nach P CFK , so dass folgendes gilt: Die Übersetzung erhält den Typ und es gilt: Zu jedem Ausdruck e : num in PCF existiert ein Programm Pe in P CFK , so dass maine eine Normalform n hat, gdw. e die Normalform n hat. 8.1 8.1.1 Übersetzung P CF → PCFK Gegeben ein geschlossener Ausdruck e in PCF. Zunächst mache alle gebundenen Variablennamen disjunkt, und achte darauf, dass dies im Algorithmus zu jeder Zeit eingehalten wird. Von unten her verwandle Lambda-Ausdrücke und µ-Ausdrücke in Kombinator-Ausdrücke. 34 TIDS 2, SS13, Kapitel 1, vom 11.4.2013 Fall: ein innerer Ausdruck ist ein Lambda-Ausdruck. Betrachte einen maximalen Ausdruck t. D.h. einen solchen, der nicht selbst genau der Rumpf eines Lambda-Ausdrucks ist. • Wenn t keine freien Variablen enthält, dann erzeuge eine Superkombinatordefinition: λx1 , . . . xn .e wird zu neuerkomb x1 . . . xn = e. Ersetze den Lambda-Ausdruck durch • Wenn t freie Variablen enthält, dann verwende Lambda-Lifting: Sei y eine solche freie Variable in e, dann: λx1 , . . . xn .e[y] → λz, x1 , . . . xn .e[z/y]) y Fall: ein innerer Ausdruck ist ein µ-Ausdruck. • Wenn t ≡ µx.e und e keine freien Variablen außer x enthält, dann erzeuge eine neue Superkombinatordefinition: neuerkomb = e[neuerkomb/x]. und ersetze den µ-Ausdruck in t durch neuerkomb. • Wenn t ≡ µx.e und µx.e genau die genau freien Variablen y1 , . . . , yn enthält, dann erzeuge eine neue Superkombinatordefinition: neuerkomb y1 . . . yn = e[(neuerkomb y1 . . . yn )/x]. und ersetze den µ-Ausdruck in t durch (neuerkomb y1 . . . yn ). Der Gesamtausdruck e wird als Definition main = e in die Menge der Definition aufgenommen 8.1.2 PCFK → PCF Das Ziel dieser Übersetzung ist es, Namen durch Ausdrücke zu ersetzen. Betrachte alle Definitionen als großes letrec. letrec name1 = λx1,1 , . . . , x1,m1 .d1 , ..., namen = λxn,1 , . . . , xn,mn .dn in main Die Kombinatoren lassen sich jetzt durch den n-dimensionalen Satz von Bekić als Fixpunktausdrücke darstellen und damit hat man einen einzigen Ausdruck, der mit λ und µ-Ausdrücken in PCF dargestellt werden kann. Bemerkung 8.1 Für Funktionstypen gilt eine zum Verhalten von Ausdrücken vom Basistyp analoge Aussage: Zu jedem Ausdruck e : τ1 → τ2 → . . . τn → num existiert ein übersetztes Programm und ein Ausdruck maine , so dass für alle Ausdrücke ei : τi und deren Übersetzung gilt: e e1 . . . en hat Normalform m gdw. maine maine,1 . . . maine,n die Normalform m hat. Beispiel 8.2 1. PCF → PCFK : e = µx : num.(µy : num.if zero? x then y else x ) ergibt: TIDS 2, SS13, Kapitel 1, vom 11.4.2013 35 f x = if zero? x then f x else x) g = f g 2. PCFK → PCF: Maximumsfunktion: max x y = if zero? x then y else if zero? y then y else succ (max (pred x) (pred y)) max = µm. if zero? x then y else if zero? y then y else succ(m(pred x)(pred y)) Bemerkung 8.3 Eigenschaften der Übersetzung: Für Ausdrücke vom Basistyp num gilt, dass die Ausdrücke vor und nach der Übersetzung jeweils zu demselben Wert reduziert und auch das gleiche Terminierungsverhalten haben. D.h. Für Basistypen ist die Übersetzung operational gleich, die Anzahl der Reduktionsschritte ist gleich bis auf konstante Faktoren, die im wesentlichen die evtl. unterschiedliche Zählweise bei Superkombinatorreduktion und β- bzw. µ-Reduktion ausdrücken. Für allgemeinere Typen gilt das nicht: Die Anzahl der Schritte ist etwas weniger korreliert. Z.B. λxλy.x wird übersetzt in K x y = x. Aber: der Ausdruck (K 1) ist in WHNF in PCFK , während (λxλy.x) 1 in einem Schritt zu λy.1 reduziert. Die Anzahl der zusätzlichen Reduktionsschritte ist aber begrenzt durch die Größe des Programms und des Ausdrucks. Folgerung: Verschränkte Rekursion entspricht der Anwendung des µ-Operator auf Ausdrücken mit freien Variablen.