¨ Ubersetzerbau Algebraic Compiler Construction Peter Padawitz, TU Dortmund Webseite zur LV: fldit-www.cs.uni-dortmund.de/ueb.html Thursday 6th November, 2014 01:25 1 Inhalt Zur Navigation auf die Titel – nicht auf die Seitenzahlen (!) – klicken. Mit ∗ markierte Abschnitte bzw. Kapitel werden zur Zeit in der LV nicht behandelt. 1 Einleitung 2 Algebraische Modellierung - Mengen und Typen - Signaturen - 2.1 Konstruktive Signaturen - 2.2 Destruktive Signaturen - Σ-Terme - Σ-Algebren - 2.6 Die Algebren Regword (CS) und Bool - 2.7 Die Algebren Beh(X, Y ), Pow (X), Lang(X) und Bro(CS) - 2.8 Erreichbarkeitsfunktion - Freie und initiale Algebren, Termauswertung - Finale Algebren 8 15 15 21 24 26 27 29 32 34 38 39 43 2 - 2.11 Von Erkennern regula¨rer Sprachen zu Compilern regula¨rer Ausdru¨cke - 2.12 *Initiale Automaten - 2.14 *Baumautomaten 3 *Rechnen mit Algebren - Unter- und Bildalgebren - Kongruenzen und Quotienten - Automatenminimierung durch Quotientenbildung - Transitionsmonoid und syntaktische Kongruenz - 3.6 Termsubstitution - Terma¨quivalenz und Normalformen - 3.10 Normalformen regul¨arer Ausdru¨cke - 3.11 Die Brzozowski-Gleichungen - 3.12 Aus Normalformen gebildete Erkenner regul¨arer Sprachen - 3.13 Erkenner regul¨arer Sprachen sind endlich 4 Kontextfreie Grammatiken (CFGs) - Nicht-linksrekursive CFGs - Abstrakte Syntax - Wort- und Ableitungsbaumalgebra 47 52 54 56 56 60 65 66 68 70 75 76 78 79 82 87 90 98 3 - Modelle der Ausdru¨cke von JavaLight 5 Interpreter, Parser und Compiler - 5.1 Funktoren und Monaden 104 109 111 6 LL-Compiler 129 7 LR-Compiler 150 8 Haskell: Typen und Funktionen 177 9 Haskell: Listen 186 10 Haskell: Datentypen und Typklassen 197 - Typklassen 208 11 Algebren in Haskell 215 - 11.7 Die Wortalgebra von JavaLight - 11.8 Das Zustandsmodell von JavaLight - 11.9 *Die Ableitungsbaumalgebra von JavaLight ¨ 12 Attributierte Ubersetzung - 12.1 *Bin¨ardarstellung rationaler Zahlen - 12.2 *Strings mit Hoch- und Tiefstellungen 225 226 229 232 234 235 4 - 12.3 - 12.4 - 12.5 ¨ *Ubersetzung regul¨arer Ausdru¨cke in erkennende Automaten *Darstellung von Termen als hierarchische Listen Assemblersprache StackCom ∗ und JavaLight-Algebra javaStack 237 244 247 13 JavaLight+ = JavaLight + I/O + Deklarationen + Prozeduren 254 - 13.1 Assemblersprache mit I/O und Kelleradressierung - 13.2 Grammatik und abstrakte Syntax von JavaLight+ - 13.3 JavaLight+-Algebra javaStackP 254 261 265 14 *Mehrp¨assige Compiler - 14.1 LAG-Algorithmus 15 Monaden in Haskell - 15.1 - 15.2 - 15.3 - 15.4 - 15.6 - 15.5 Typen und Typvariablen h¨oherer Ordnung Die Typklasse Monad Die Maybe-Monade Die Listenmonade Transitionsmonaden Monaden-Kombinatoren 16 Monadische Compiler 287 293 295 295 297 299 301 302 310 312 5 - 16.1 - 16.2 - 16.3 - 16.4 - 16.5 - 16.6 - 16.7 - 16.8 Compilerkombinatoren Monadische Scanner Compiler fu¨r Basismengen Monadische LL-Compiler Generischer JavaLight-Compiler Korrektheit des JavaLight-Compilers Testumgebung fu¨r den JavaLight-Compiler Generischer XMLstore-Compiler 315 319 320 321 323 325 329 331 17 *Induktion, Coinduktion und rekursive Gleichungen 334 - Induktion - Induktive L¨osungen - Coinduktion - Coinduktive Lo¨sungen 18 *Iterative Gleichungen - 18.2 Das iterative Gleichungssystem einer CFG - 18.5 Erweiterung von Bro(CS) um Erkenner kontextfreier Sprachen 19 *Interpretation in stetigen Algebren - 19.1 Schleifensemantik 334 336 340 347 363 364 369 376 381 6 - 19.2 Semantik der Assemblersprache StackCom ∗ - Σ-B¨aume - Cosignaturen und ihre Algebren 20 *Cotermalgebren - Σ-Coterme - 20.6 Cotermbasierter Erkenner regul¨arer Sprachen 382 389 397 401 402 411 21 Literatur 414 22 Index 418 7 1 Einleitung Die Folien dienen dem Vor- (!) und Nacharbeiten der Vorlesung, k¨onnen und sollen aber deren regelma¨ßigen Besuch nicht ersetzen! Interne Links (einschließlich der Seitenzahlen im Index) sind an ihrer dunkelroten F¨arbung, externe Links (u.a. zu Wikipedia) an ihrer magenta-F¨arbung erkennbar. Dunkelrot gef¨arbt ist auch das jeweils erste Vorkommen einer Notation. Fettgedruckt ist ein Begriff in der Regel nur an der Stelle, an der er definiert wird. Jede Kapitelu¨berschrift und jede Seitenzahl in der rechten unteren Ecke einer Folie ist mit dem Inhaltsverzeichnis verlinkt. Namen von Haskell-Modulen wie Java.hs sind mit den jeweiligen Programmdateien verknu¨pft. Ein Scanner (Programm zur lexikalischen Analyse) fasst Zeichen zu Symbolen zusammen, u¨bersetzt also eine Zeichenfolge in eine Symbolfolge. Bezu¨glich der Grammatik, die der nachfolgenden Syntaxanalyse zugrundeliegt, heißen die Symbole auch Terminale. Man muss unterscheiden zwischen Symbolen, die Komponenten von Operatoren sind (if, then, =, etc.), und Symbolen wie 5 oder True, die der Scanner einer Basismenge (Int, Float, Bool, Identifier, etc.) zuordnet. In der Grammatik kommt ein solches Symbol nicht vor, jedoch der Namen der Basismenge, der es angeh¨ort. 8 Ein Parser (Programm zur Syntaxanalyse) u¨bersetzt eine Symbolfolge in einen Syn¨ taxbaum, der den fu¨r ihre Ubersetzung in eine Zielsprache notwendigen Teil ihrer Bedeutung (Semantik) wiedergibt. Der Parser scheitert, wenn die Symbolfolge in diesem Sinne bedeutungslos ist. Er orientiert sich dabei an einer (kontextfreien) Grammatik, die korrekte von bedeutungslosen Symbolfolgen trennt und damit die Quellsprache definiert. Ein Compiler (Programm zur semantischen Analyse und Codeerzeugung) u¨bersetzt ein Programm einer Quellsprache in ein semantisch ¨aquivalentes Programm einer Zielsprache. Ein Interpreter • wertet einen Ausdruck in Abh¨angigkeit von einer Belegung seiner Variablen aus bzw. • fu¨hrt eine Prozedur in Abh¨angigkeit von einer Belegung ihrer Parameter aus. Letzteres ist ein Spezialfall von Ersterem. Deshalb werden wir einen Interpreter stets als die Faltung von Termen (Ausdru¨cken, die aus Funktionssymbolen einer Signatur bestehen) in einer Algebra (Interpretation der Signatur) modellieren. 9 Auch Scanner, Parser und Interpreter sind Compiler, insofern sie Objekte von einer Repr¨asentation in eine andere u ¨bersetzen. Die Zielrepr¨asentation eines Interpreters ist i.d.R. eine Funktion, die Eingabedaten verarbeitet. Daher liegt es nahe, alle o.g. Programme nach denjenigen Prinzipien zu entwerfen, die bei Compilern im engeren Sinne Anwendung finden. Die Entwicklung dieser Prinzipien und ihrer formalen Grundlagen wurzelt in der mathematischen Logik und dort vor allem in der Theorie formaler Sprachen und der Universellen Algebra, deren Anwendung im Compilerbau in dieser LV eine zentra¨ le Rolle spielen wird. Die Aufgabe, Ubersetzer zu schreiben, hat auch entscheidend die Weiterentwicklung von Programmiersprachen, insbesondere der logischen und funktionalen, vorangetrieben. Insbesondere Konzepte der universellen Algebra und der funktionalen Programmierung im ¨ Ubersetzerbau erlauben es, bei Entwurf und Implementierung von Scannern, Parsern, Compilern und Interpretern a¨hnliche Methoden einzusetzen. So ist z.B. ein wie oben definierter Interpreter gleichzeitig ein Compiler, der den Ausdruck oder die Prozedur in eine Funktion u¨bersetzt, die eine Belegung auf einen Wert des Ausdrucks bzw. eine vom Aufruf der Prozedur erzeugte Zustandsfolge abbildet. 10 Der algebraische Ansatz kla¨rt nicht nur die Bezu¨ge zwischen den oben genannten Programmen, sondern auch eine Reihe weiterer in der Compilerbauliteratur meist sehr speziell definierter und daher im Kern vager Begriffe wie attributierter Grammatiken und mehrp¨assiger Compilation. Mehr Beachtung als die nicht wirklich essentielle Trennung zwischen Scanner, Parser, Compiler und Interpreter verdienen die Ans¨atze, Compiler generisch (neu-informatisch: agil) zu entwerfen, so dass sie mit verschiedenen Quellsprachen und - wichtiger noch - mehreren Zielsprachen instanziiert werden k¨onnen. Es handelt sich dann im Kern um einen Parser, der keine Symbol-, sondern Zeichenfolgen verarbeitet und ohne den klassischen Umweg u¨ber Syntaxb¨aume gleich die gewu¨nschten Zielobjekte/programme aufbaut. Sein front end kann mit verschiedenen Scannern verknu¨pft werden, die mehr oder weniger detaillerte Symbolund Basistypinformation erzeugen, w¨ahrend sein back end mit verschiedenen Interpretationen ein und derselben - u¨blicherweise als kontextfreie Grammatik eingegebenen - Signatur instanziiert werden kann, die verschiedenen Zielsprachen entsprechen. Die beiden folgenden Grafiken skizzieren die Struktur des algebraischen Ansatzes und seine Auswirkung auf die Generizit¨at der Programme. Auf die Details wird in den darauffolgenden Kapiteln eingegangen. Dabei wird die jeweilige Funktionalita¨t der o.g. Programme in mathematisch pr¨azise Definitionen gefasst, auf denen Entwurfsregeln aufbauen, deren Befolgung automatisch zu korrekten Compilern, Interpretern, etc. fu¨hren. 11 Erkenner Programmeigenschaften Σ(G)-Formeln Bool Σ(G)-Algebren Quellsprache L(G) Verifizierer Unparser konkrete Syntax Grammatik G abstrakte Syntax Signatur Σ(G) Syntaxbäume Termalgebra TΣ(G) Optimierer allgemein: Termfaltung Parser Quellsprache L(G) Unparser Compiler Ableitungsbäume Abl(G) optimierte Syntaxbäume Zielsprache Von konkreter u ¨ber abstrakte Syntax zu Algebren 12 Menge Faltung kontextfreie Sprache Parser Termalgebra Faltung Menge Faltung Menge Algebra kontextfreie Sprache Parser Termalgebra generische Faltung Algebra Algebra Algebra kontextfreie Sprache generischer Compiler Algebra Algebra Der Weg zum generischen Compiler 13 fact =1; while x > 1 {fact = fact*x; x = x-1;} Eingabewort (Element der JavaLight-Algebra javaWord) parse fold Seq Assign Embed fact Sum Loop Prod NilS EmbedC EmbedI NilP Block EmbedL 1 Seq Atom Sum > Sum Prod NilS Var NilP x Assign Embed fact Sum Assign Prod NilS EmbedI NilP 1 Prod NilS Var Prodsect x Sum Prod fact * Var NilP Var NilP x x Syntaxbaum (Element der JavaLight-Algebra javaTerm) Sumsect - Prod NilS EmbedI NilP 1 fold fold Zustandstransitionsfunktion (Element der JavaLight-Algebra javaState) Assemblerprogramm (Element der JavaLight-Algebra javaStack) 14 2 Algebraische Modellierung Mengen und Typen 1 und 2 bezeichnen nicht nur Zahlen, sondern auch die ein- bzw. zweielementige Menge {} und {0, 1}. Die Elemente 0 und 1 der Menge 2 stehen oft fu¨r die Wahrheitswerte false bzw. true. Sei n > 0. Das n-stellige Produkt von Mengen A1, . . . , An ist wie folgt definiert: A1 × · · · × An =def {(a1, . . . , an) | ai ∈ Ai, 1 ≤ i ≤ n} (a1, . . . , an) heißt n-Tupel mit den Komponenten a1, . . . , an. Das nullstellige Produkt wird mit der Menge 1 = {} gleichgesetzt. Sind alle A1, . . . , An gleich einer Menge A, dann schreibt man An fu¨r A1 × · · · × An und notiert die Elemente von An auch als Listen, d.h. mit eckigen anstelle runder Klammern, oder als W¨ orter, d.h. ohne Klammern und Kommas, also a1 . . . an anstelle von (a1, . . . , an). Fu¨r alle n > 0 und w ∈ An bezeichnet |w| =def n die L¨ ange von w. Fu¨r alle 1 ≤ i ≤ n ist die Projektion πi : A1 × · · · × An → Ai wie folgt definiert: Fu¨r alle a = (a1, . . . , an) ∈ A1 × · · · × An, πi(a) =def ai. Im Fall n = 1 entspricht π1 der Identit¨ at idA1 auf A1. 15 Die Produktextension hf1, . . . , fni : A → B1 × · · · × Bn von n Funktionen f1 : A → B1, . . . , fn : A → Bn ist wie folgt definiert: Fu¨r alle a ∈ A, hf1, . . . , fni(a) =def (f1(a), . . . , fn(a)). Das Produkt f1 × · · · × fn : A1 × · · · × An → B1 × · · · × Bn von n Funktionen f1 : A1 → B1, . . . , fn : An → Bn ist wie folgt definiert: Fu¨r alle (a1, . . . , an) ∈ A1 × · · · × An, (f1 × · · · × fn)(a1, . . . , an) =def (f1(a1), . . . , fn(an)). Plusoperator + und Sternoperator ∗ A+ =def S n n>0 A + A∗ =def 1 ∪ A Die Konkatenation · : A∗ × A∗ → A∗ ist wie folgt induktiv definiert: Fu¨r alle s, (a1, . . . , am), (b1, . . . , bn) ∈ A∗ und B, C ⊆ A∗. · s = s, (a1, . . . , am) · = (a1, . . . , am), (a1, . . . , am) · (b1, . . . , bn) = (a1, . . . , am, b1, . . . , bn), B · C = {s · s0| s ∈ B, s0 ∈ C}. 16 Die n-stellige Summe oder disjunkte Vereinigung von Mengen A1, . . . , An ist wie folgt definiert: A1 + · · · + An = A1 ] · · · ] An =def {(a, i) | a ∈ Ai, 1 ≤ i ≤ n} Die zweite Komponente eines Paares (a, i) von A1 + · · · + An dient der Unterscheidung der Herkunft von a. Gibt es a ∈ A1 ∪ · · · ∪ An und 1 ≤ i, j ≤ n mit i 6= j und a ∈ Ai ∩ Aj , dann gibt es mindestens zwei Kopien von a in A1 + · · · + An, na¨mlich (a, i) und (a, j). Gibt es kein solches a, dann heißen A1 . . . , An (paarweise) disjunkt, und man verzichtet in der Regel auf die zweite Komponente der Elemente von A1 +· · ·+An, setzt also A1 +· · ·+An mit A1 ∪ · · · ∪ An gleich. Die Mengen 1+1 und 2 sind isomorph. Beweis! Die Potenzmenge P(A) einer Menge A ist die Menge aller Teilmengen von A: e ∈ P(A) ⇔def e ⊆ A Fu¨r Mengen A und B bezeichnen A → B und B A bzw. A (→ B die Mengen der totalen bzw. partiellen Funktionen von A nach B. Man schreibt f : A → B bzw. f : A (→ B anstelle von f ∈ (A → B) bzw. f ∈ (A (→ B). Im zweiten Fall bezeichnet def (f ) den Definitionsbereich von f . 17 Die Definition einer Funktion kann auf unterschiedliche Weise pra¨sentiert werden. Z.B. beschreiben (1)-(3) dieselbe (totale) Funktion f : f :N×N → R (m, n) 7→ m ∗ n/2 (1) Fu¨r alle m, n ∈ N, f (m, n) =def m ∗ n/2 (2) f =def λ(m, n).(m ∗ n/2) (3) (3) pr¨asentiert f als λ-Abstraktion. Fu¨r alle C ⊆ A und D ⊆ B, f (C) =def {f (a) | a ∈ C}, f −1(D) =def {a ∈ A | f (a) ∈ D}, f |C : C (Bild von C unter f ) (Urbild von D unter f ) → B c 7→ f (c). Aufgabe Zeigen Sie: (f injektiv und f (C) = D) ⇒ f −1(D) = C, (f surjektiv und f −1(D) = C) ⇒ D = f (C). o 18 Sei A eine Menge und χ : P(A) → 2A C 7→ λa.if a ∈ C then 1 else 0. Fu¨r alle C ⊆ A heißt charakteristische Funktion von C bzgl. A. χ ist bijektiv: Die Umkehrfunktion χ−1 bildet f ∈ 2A auf die Menge aller a ∈ A mit f (a) = 1 ab. Sei f : A → B, a ∈ A, b ∈ B und f [b/a] : A → B a0 7→ if a0 = a then b else f (a0). Im Folgenden definieren wir induktiv eine Menge von Typen als Ausdru¨cke, die mit Hilfe einiger der oben definierten Mengenoperatoren aufgebaut sind. Sei S eine Menge von Sorten und BS eine Menge nichtleerer Basismengen. Die Menge T(S,BS) der Typen (erster Ordnung) u ¨ ber S und BS ist induktiv definiert: • S ∪ BS ⊆ T(S,BS). • Fu¨r alle n > 1 und e1, . . . , en ∈ T(S,BS), e1 × · · · × en ∈ T(S,BS). • Fu¨r alle e ∈ T(S,BS) und X ∈ BS, eX ∈ T(S,BS). 19 e1 × · · · × en mit n = 0 wird mit der Basismenge 1 = {} gleichgesetzt. Ein Typ der Form e1 wird mit e identifiziert. Sei S eine Menge. Eine S-sortige Menge ist ein Tupel A = (As)s∈S von Mengen. A wird auch als Bezeichnung fu¨r die Vereinigung aller As verwendet. Eine Teilmenge B von A, geschrieben: B ⊆ A, ist eine S-sortige Menge mit Bs ⊆ As fu¨r alle s ∈ S. Eine S-sortige Menge A wird wie folgt zur T(S,BS)-sortigen Menge geliftet: Sei X ∈ BS, s ∈ S und e, e1, . . . , en ∈ T(S,BS). AX =def X Ae1×···×en =def Ae1 × . . . × Aen AeX =def AX e (Basismenge) (Produkt) (Potenz) Seien A und B S-sortige Mengen. Eine S-sortige Funktion h : A → B ist eine S-sortige Menge derart, dass fu¨r alle s ∈ S hs eine Funktion von As nach Bs ist. 20 h : A → B wird wie folgt zur T(S,BS)-sortigen Funktion geliftet: Sei X ∈ BS und e, e1, . . . , en ∈ T(S,BS). • hX : AX → BX ist die Identit¨atsfunktion auf X. (Basismenge) • he1×···×en : Ae1×···×en → Be1×···×en bildet (a1, . . . , an) ∈ Ae1 × . . . × Aen auf (he1 (a1), . . . , hen (an)) ab. (Produkt) • heX : AeX → BeX bildet g : X → Ae auf he ◦ g : X → Be ab. (Potenz) Lemma RLIFT Fu¨r alle S-sortigen Funktionen g, h : A → B, e ∈ T(S,BS) und die S-sortige Teilmenge C = {a ∈ A | g(a) = h(a)} gilt Ce = {a ∈ Ae | ge(a) = he(a)}. o Signaturen Eine Signatur Σ = (S, BS, BF, F ) besteht aus Mengen S von Sorten, BS von Basismengen und BF von Basisfunktionen f : X → Y mit X, Y ∈ BS sowie einer Menge F – Operationen genannter – typisierter Funktionssymbole f : e → e0 mit e, e0 ∈ T(S,BS). Fu¨r alle f : e → e0 ∈ F heißt dom(f ) = e Domain und ran(f ) = e0 Range von f . 21 f ∈ F heißt Konstruktor, wenn ran(f ) ∈ S ist und es e, e1, . . . , en ∈ S ∪ BS gibt mit dom(f ) = e1 × · · · × en. f ∈ F heißt Destruktor, wenn dom(f ) ∈ S ist und es e ∈ S ∪ BS und X ∈ BS ∪ {1} gibt mit ran(f ) = eX . Σ heißt konstruktive bzw. destruktive Signatur, wenn F aus Konstruktoren bzw. Destruktoren besteht. Konstruktoren und Destruktoren dienen der Erzeugung und Synthese bzw. Transformation, Attributierung und Analyse von Elementen der Tr¨agermengen, die eine Σ-Algebra (s.u.) den Sorten von Σ zuordnet. In anderen Anwendungen als dem Compilerbau k¨onnen andere Typen als die o.g. den Domain eines Konstruktors bzw. den Range eines Destruktors bilden. Die hier ausgew¨ahlten Typen decken jedoch alles ab, was u¨blicherweise zur mathematischen ¨ Modellierung einer Programmiersprache, ihrer Ubersetzer sowie der (abstrakten) Maschinen, die ihre Programme ausfu¨hren, erforderlich ist. Die abstrakte Syntax einer kontextfreien Grammatik (siehe Kapitel 4) ist eine konstruktive Signatur, w¨ahrend Parser und Compiler fu¨r solche Grammatiken auf Automatenmodellen basieren, die destruktive Signaturen interpretieren. 22 Andere formale Modelle wie z.B. (unendliche) Prozessb¨aume, Kontrollflussgraphen, objektorientierte Klassendiagramme oder Hyperdokumente lassen sich ebenfalls als Interpretationen destruktiver Signaturen darstellen. Allerdings ben¨otigt man dort allgemeinere Destruktor-Ranges. Fu¨r den einsortigen Fall ohne Basismengen werden in diesem Kapitel behandelte Begriffe auch im Kapitel Terme und Algebren von [26] eingefu¨hrt. Die Sorten einer Signatur werden durch Mengen interpretiert, die Funktionssymbole durch Funktionen auf diesen Mengen (siehe Σ-Algebren). In den folgenden Beispielen steht hinter 1 eine Standardinterpretation der jeweiligen Signatur. Die Menge BF der Basisfunktionen ist in allen Beispielen von 2.1 und 2.2 leer. Seien X und Y Mengen von Konstanten (Zahlen, Zeichen, etc.) und CS eine endliche Menge nichtleerer Konstantenmengen. 23 2.1 Konstruktive Signaturen • Mon 1 Monoid S = {mon}, BS = ∅, F = { one : 1 → mon, mul : mon × mon → mon }. • Nat 1 natu¨rliche Zahlen S = {nat}, BS = ∅, F = { zero : 1 → nat, succ : nat → nat }. • Reg(CS) 1 regula¨re Ausdru¨cke u¨ber CS S = {reg}, BS = ∅, F = { eps : 1 → reg, mt : 1 → reg, par : reg × reg → reg, (parallel composition) seq : reg × reg → reg, (sequential composition) iter : reg → reg } ∪ (iteration) { C : 1 → reg | C ∈ CS } Der nullstellige Konstruktor C steht fu¨r einen Namen der Menge C. 24 • List(X) 1 endliche Sequenzen von Elementen aus X S = {list}, BS = {X}, F = { nil : 1 → list, cons : X × list → list }. • Bintree(X) 1 bin¨are B¨aume endlicher Tiefe mit Knotenmarkierungen aus X S = {btree}, BS = {X} F = { empty : 1 → btree, bjoin : btree × X × btree → btree }. • Tree(X) 1 B¨aume endlicher Tiefe mit Knotenmarkierungen aus X S = {tree, trees}, BS = {X}, F = { join : X × trees → tree, nil : 1 → trees, cons : tree × trees → trees }. 25 2.2 Destruktive Signaturen • DAut(X, Y ) 1 deterministische Moore-Automaten S = { state }, (Zustandsmenge) BS = { X, Y }, F = { δ : state → stateX , β : state → Y }. (Ein- bzw. Ausgabemenge) ¨ (Uberf u¨hrungsfunktion) (Ausgabefunktion) • Acc(X) = b DAut(X, 2) 1 erkennende Automaten (Akzeptoren) S = {reg}, BS = {X, 2}, F = { δ : reg → reg X , β : reg → 2 }. • Stream(X) = b DAut(1, X) 1 unendliche Listen von Elementen aus X S = {list}, BS = {X}, F = { head : list → X, tail : list → list }. • Infbintree(X) 1 bin¨are B¨aume unendlicher Tiefe mit Knotenmarkierungen aus X S = {btree}, BS = {X}, F = { root : btree → X, left, right : btree → btree }. 26 Σ-Terme Sei Σ = (S, BS, BF, F ) eine Signatur und V eine (S ∪BS)-sortige Menge von Variablen. Die T(S,BS)-sortige Menge TΣ(V ) der Σ-Terme u ¨ ber V ist induktiv definiert: • Fu¨r alle s ∈ S ∪ BS, Vs ⊆ TΣ(V )s. • Fu¨r alle X ∈ BS, X ⊆ TΣ(V )X . • Fu¨r alle c : 1 → e ∈ BF ∪ F , c ∈ TΣ(V )e. • Tuplung: Fu¨r alle n > 1, e1, . . . , en ∈ T(S,BS) und ti ∈ TΣ(V )ei , 1 ≤ i ≤ n, (t1, . . . , tn) ∈ TΣ(V )e1×···×en . • Funktionsapplikation: Fu¨r alle f : e → e0 ∈ BF ∪F und t ∈ TΣ(V )e, f t ∈ TΣ(V )e0 . • λ-Abstraktion: Fu¨r alle X ∈ BS und e ∈ T(S,BS), x ∈ VX und t ∈ TΣ(V )e, λx.t ∈ TΣ(V )eX . • Termapplikation: Fu¨r alle X ∈ BS, e ∈ T(S,BS), t ∈ TΣ(V )eX und u ∈ TΣ(V )X , t(u) ∈ TΣ(V )e. • Konditional: Fu¨r alle e ∈ T(S,BS), t ∈ TΣ(V )2 und u, v ∈ TΣ(V )e, ite(t, u, v) ∈ TΣ(V )e. In der u¨blichen Baumdarstellung von Termen fasst man fu¨r alle x ∈ V λx als einstelliges Funktionssymbol und die Applikation einer Funktion auf ein Argument – wie in Haskell 27 – als zweistelliges Funktionssymbol ($) auf. Z.B. entspricht der Term (λx.u)(v) dem – als String notierten – Baum (λx)(u)$v. var(t) bezeichnet die Menge aller Variablen von t, x ∈ var(t) ist frei, wenn t keinen Teilterm λx.u mit x ∈ var(u) hat. free(t) bezeichnet die Menge aller freien Variablen von t. Σ-Terme ohne Variablen heißen Σ-Grundterme. TΣ bezeichnet die Menge der Σ-Grundterme. Gibt es kein f ∈ F mit dom(f ) ∈ T(∅,BS), dann ist TΣ leer! Beispiel 2.3 (siehe 2.1) Die Menge TNat der Nat-Grundterme ist wie folgt induktiv definiert: • zero ∈ TNat,nat. • Fu¨r alle t ∈ TNat,nat, succ(t) ∈ TNat,nat. o 28 Mehrsortigkeit la¨sst sich mit Hilfe von Pra¨dikaten simulieren. So wird z.B. die Sorte nat, die die Menge N aller natu¨rlichen Zahlen repr¨asentiert, durch ein Pr¨adikat isNat mit den Axiomen isNat(zero) isNat(x) ⇒ isNat(succ(x)) wiedergegeben. Im Rahmen einer Nat umfassenden Signatur Σ ist dann TΣ,nat die Menge derjenigen Σ-Grundterme, die o.g. Axiome erfu¨llen. Umgekehrt erspart uns die Sorte nat das Pra¨dikat isNat und seine Axiome. Σ-Algebren Sei Σ = (S, BS, BF, F ) eine Signatur. Eine Σ-Algebra A besteht aus einer – h¨aufig auch mit A bezeichneten – S-sortigen Menge, der Interpretation von S, und zu jeder Operation f : e → e0 ∈ F einer Funktion f A : Ae → Ae0 , der Interpretation von f in A. Der Definitionsbereich Ae und der Wertebereich Ae0 von f A interpretieren also die Typen dom(f ) = e bzw. ran(f ) = e0 in A. Im Fall Ae = 1 schreibt man f A anstelle von f A(). Fu¨r alle s ∈ S heißt As Tr¨ agermenge (carrier set) von s. Algebren destruktiver Signaturen werden auch Coalgebren genannt. 29 Seien A und B Σ-Algebren. Eine S-sortige Funktion h ist Σ-homomorph oder ein Σ-Homomorphismus, falls h mit den Interpretationen der Funktionssymbole von Σ in A bzw. B vertr¨aglich ist, d.h. fu¨r alle f : e → e0 ∈ F gilt: he0 ◦ f A = f B ◦ he. Ist h auch bijektiv, dann sind A und B Σ-isomorph und h ist ein Σ-Isomorphismus. Aufgabe Zeigen Sie hY ◦ f = f ◦ hX fu¨r alle f : X → Y ∈ BF . Beispiel 2.4 (siehe 2.1) Die Menge N natu¨rlicher Zahlen ist Tr¨agermenge einer Nat-Algebra und auch einer List(1)Algebra: Fu¨r alle n ∈ N, zeroN = 0, succN(n) = n + 1, nilN = 0, consN(, n) = n + 1. (Nachfolgerfunktion) o 30 Beispiel 2.5 (siehe 2.1) Die Menge X ∗ der W¨orter u¨ber einer Menge X ist Tr¨agermenge einer List(X)-Algebra und auch einer Mon-Algebra: Fu¨r alle x ∈ X und v, w ∈ X ∗, nilX ∗ = , (leeres Wort) X∗ cons (x, w) = xw, (append) oneX ∗ = , ∗ mulX (v, w) = vw. (Konkatenation von W¨ortern) Auch die Menge X → X der Funktionen von einer Menge X in sich selbst (Endofunktionen auf X) ist Tr¨agermenge einer Mon-Algebra: Fu¨r alle f, g : X → X, oneX→X = idX , (Identita¨tsfunktion) mulX→X (f, g) = g ◦ f. (Funktionskomposition) Eine Mon-Algebra A heißt Monoid, wenn mulA assoziativ und oneA ein neutrales Element bzgl. mulA ist. Demnach sind X ∗ und X → X Monoide. o 31 2.6 Die Algebren Regword (CS) und Bool (siehe 2.1) Sei CS1 = {c | {c} ∈ CS}, CS>1 = {C ∈ CS | |C| > 1} und syms(CS) = {, ∅, +, ∗, (, )} ∪ CS1 ∪ CS>1. Die folgende Reg(CS)-Algebra Regword (CS) beschreibt die u¨blichen Wortdarstellungen regul¨arer Ausdru¨cke wie z.B. aab∗+c(aN)∗: Regword (CS)reg = syms(CS)+ × Z. Fu¨r alle c ∈ CS1, C ∈ CS>1 und v, w ∈ syms(CS)+ und i, k ∈ Z, epsRegword = (, 3), mtRegword = (∅, 3), {c} C Regword Regword = (c, 3), = (C, 3), parRegword ((v, i), (w, k)) = (v+w, 0), seq Regword ((v, i), (w, k)) = (enclose(v, i, 1) enclose(w, k, 1), 1), iterRegword (w, i) = (enclose(w, i, 2)∗, 2), wobei enclose(w, i, k) = if i < k then (w) else w. 32 Die Faltung von Reg(CS)-Termen in Regword (CS) (s.u.) liefert Paare (w, i) ∈ syms(CS)+ × Z, wobei i die (u¨bliche) Priorit¨at des fu¨hrenden regul¨aren Operators von w ist. Die Haskell-Funktion regWord von Compiler.hs implementiert Regword (String). Auch 2 = {0, 1} ist die Tr¨agermenge der Reg(CS)-Algebra Bool : Fu¨r alle C ∈ CS und b, c ∈ 2, epsBool = 1, iterBool (b) = 1, mtBool = 0, C Bool = 0, parBool (b, c) = max{b, c}, seq Bool (b, c) = b ∗ c. o 33 2.7 Die Algebren Beh(X, Y ), Pow (X), Lang(X) und Bro(CS) (siehe 2.2) Seien X und Y Mengen. Funktionen von X ∗ nach Y nennen wir Verhaltensfunktionen (behavior functions). Die DAut(X, Y )-Algebra Beh(X, Y ) ist wie folgt definiert: ∗ Beh(X, Y )state = Y X . Fu¨r alle f : X ∗ → Y , x ∈ X und w ∈ X ∗, δ Beh (f )(x)(w) = f (xw) und β Beh (f ) = f (). δ Beh (f ) und β Beh (f ) werden auch Ableitung (derivative) bzw. Anfangswert (initial value) von f genannt [2, 15], weil f durch δ Beh (f ) und f () eindeutig bestimmt ist, so wie eine (bei 0) analytische Funktion auf R durch deren (erste) Ableitung und deren Wert an der Stelle 0 eindeutig bestimmt ist. Allgemein nennen wir die Elemente der Tr¨agermenge Astate einer DAut(X, Y )-Algebra A Zust¨ ande. Beh(X, 2) ist im Haskell-Modul Compiler.hs durch accBeh implementiert. ∗ Eine zu Beh(X, 2)state = 2X bijektive Menge ist die Potenzmenge P(X ∗) (siehe Mengen und Typen). Daraus ergibt sich die Acc(X)-Algebra Pow (X) mit Pow (X)state = P(X ∗). 34 Fu¨r alle L ⊆ X ∗ und x ∈ X, ( δ Pow ∗ (L)(x) = {w ∈ X | xw ∈ L} und β Pow (L) = 1 falls ∈ L, 0 sonst. ∗ Aufgabe Zeigen Sie, dass die Funktion χ : P(X ∗) → 2X , die jeder Teilmenge von X ∗ ihre charakteristische Funktion zuordnet (siehe Mengen und Typen), ein Acc(X)-Homomorphismus von Pow (X) nach Beh(X, 2) ist. Sei CS eine nichtleere Menge von Konstantenmengen und X = S CS. P(X ∗) ist nicht nur die Tr¨agermenge der Acc(X)-Algebra Pow (X), sondern auch der folgendermaßen definierten Reg(CS)-Algebra Lang(X) der Sprachen u ¨ ber X: Lang(X)reg = P(X ∗). Fu¨r alle C ∈ CS und L, L0 ⊆ X ∗, epsLang = 1, mtLang = ∅, C Lang = C, parLang (L, L0) = L ∪ L0, 35 seq Lang (L, L0) = L · L0, iterLang (L) = L∗ . Da die Elemente der Tr¨agermenge P(X ∗) von Pow (X) und Lang(X) unendliche Mengen sein k¨onnen, ist eine direkte Implementierung dieser Algebren in Haskell nicht m¨oglich. Stattdessen werden im Haskell-Modell Compiler.hs die Bildalgebren χ(Pow (X)) = Beh(X, 2) und χ(Lang(X)) durch accBeh bzw. regBeh implementiert (siehe Unter- und Bildalgebren). Sie existieren, weil χ sowohl Acc(X)- als auch Reg(CS)-homomorph ist. Aufgabe Zeigen Sie, dass χ Acc(X)- und Reg(CS)-homomorph ist. TReg(CS) ist nicht nur Tr¨agermenge einer Reg(CS)-Algebra, sondern auch der BrzozowskiAutomat genannten Acc(X)-Algebra Bro(CS) ( (accBroz in Compiler.hs); siehe [2, 15]): Bro(CS)reg = TReg(CS),reg . Die Interpretationen von δ und β werden induktiv (u¨ber dem Aufbau von Reg(CS)Termen) definiert: 36 Fu¨r alle C ∈ CS und t, u ∈ TReg(CS), δ Bro (eps) = λx.mt, δ Bro (mt) = λx.mt, δ Bro (C) = λx.ite(χ(C)(x), eps, mt), δ Bro (par(t, u)) = λx.par(δ Bro (t)(x), δ Bro (u)(x)), δ Bro (seq(t, u)) = λx.par(seq(δ Bro (t)(x), u), if β Bro (t) = 1 then δ Bro (u)(x) else mt), δ Bro (iter(t)) = λx.seq(δ Bro (t)(x), iter(t)), β Bro (eps) = 1, β Bro (mt) = 0, β Bro (C) = 0, β Bro (par(t, u)) = max{β Bro (t), β Bro (u)}, β Bro (seq(t, u)) = β Bro (t) ∗ β Bro (u), β Bro (iter(t)) = 1. o 37 Obgleich Destruktoren stets durch totale Funktionen interpretiert werden, ko¨nnen auch partielle, nichtdeterministische und probabilistische Automaten als Coalgebren dargestellt werden, sofern die Beschra¨nkung der Ranges von Destruktoren auf Potenzen von S und BS aufgehoben wird. Entsprechend allgemeinere Signaturen werden in meinen LVs u¨ber Logisch-Algebraischen Systementwurf behandelt. 2.8 Erreichbarkeitsfunktion Sei A eine DAut(X, Y )-Algebra. Die Erreichbarkeitsfunktion reachA : X ∗ → (Astate → Astate) von A ist wie folgt induktiv definiert: Fu¨r alle x ∈ X und w ∈ X ∗, reachA() = idA, reachA(xw) = reachA(w) ◦ λz.δ A(z)(x). reachA ist Mon-homomorph: Fu¨r alle x ∈ X und v, w ∈ X ∗, ∗ reachA(oneX ) = reachA() = idA = oneA→A, ∗ reachA(mulX (, w)) = reachA(w) = reachA(w) = reachA(w) ◦ idA = reachA(w) ◦ reachA() = mulA→A(reachA(), reachA(w)), 38 ∗ ∗ reachA(mulX (xv, w)) = reachA(xvw) = reachA(x(mulX (v, w))) ∗ = reachA(mulX (v, w)) ◦ λz.δ A(z)(x) Induktionsvor . = (mulA→A(reachA(v), reachA(w)) ◦ λz.δ A(z)(x) = (reachA(w) ◦ reachA(v)) ◦ λz.δ A(z)(x) = reachA(w) ◦ (reachA(v) ◦ λz.δ A(z)(x)) = reachA(w) ◦ reachA(xv) = mulA→A(reachA(xv), reachA(w)). Da Definitions- und Wertebereich von reachA Monoide sind, nennt man reachA einen Monoidhomomorphismus. o Freie und initiale Algebren, Termauswertung Sei Σ = (S, BS, BF, F ) eine konstruktive Signatur. Aus der Menge TΣ(V ) der Σ-Terme u¨ber V (siehe Kapitel 2) l¨asst sich wie folgt eine – auch mit TΣ(V ) bezeichnete – Σ-Algebra bilden: • Fu¨r alle s ∈ S, ist TΣ(V )s die Menge aller Σ-Terme u¨ber V ohne λ-Abstraktionen und Konditionale. • Fu¨r alle f : e → s ∈ F und t ∈ TΣ(V )e, f TΣ(V )(t) =def f t. 39 Ohne λ-Abstraktionen k¨onnen die Tr¨agermengen von TΣ(V ) auch keine Termapplikationen enthalten, so dass sie wie die Tr¨agermengen anderer Algebren auf andere Typen fortgesetzt werden k¨onnen. Insbesondere gilt TΣ(V )eX = TΣ(V )X ¨r alle e ∈ T(S,BS) und X ∈ BS. e fu TΣ(V ) ist eine freie Σ-Algebra u ¨ ber V , d.h. zu jeder Σ-Algebra A und jeder Ssortigen – Belegung (valuation) genannten Funktion g : V → A gibt es genau einen Σ-Homomorphismus g ∗ : TΣ(V ) → A mit g ∗ ◦ incV = g. Folglich ist TΣ = TΣ(∅) frei u¨ber der leeren Variablenmenge und existiert zu jeder Σ-Algebra A genau ein – Faltung genannter – Σ-Homomorphismus fold A : TΣ → A. Daher nennt man TΣ eine initiale Σ-Algebra. g ∗ und fold A sind die Einschr¨ankungen auf die Algebren TΣ bzw. TΣ(V ) der folgenden – ebenfalls mit g ∗ bezeichneten – induktiv definierten Funktion zur Auswertung beliebiger Terme einer beliebigen Signatur Σ = (S, BS, BF, F ) in einer beliebigen Σ-Algebra A: • Fu¨r • Fu¨r • Fu¨r • Fu¨r • Fu¨r alle alle alle alle alle x ∈ V , g ∗(x) = g(x). b ∈ B ∈ BS, g ∗(b) = b. c : 1 → e ∈ F , g ∗(c) = cA. n > 1 und t1, . . . , tn ∈ TΣ(V ), g ∗(t1, . . . , tn) = (g ∗(t1), . . . , g ∗(tn)). f : B → C ∈ BF und t ∈ TΣ(V )B , g ∗(f t) = f (g ∗(t)). 40 • Fu¨r alle f : e → e0 ∈ F und t ∈ TΣ(V )e, g ∗(f t) = f A(g ∗(t)). • Fu¨r alle B ∈ BS, e ∈ T(S,BS), x ∈ VB , t ∈ TΣ(V )e und b ∈ B, g ∗(λx.t)(b) = g[b/x]∗(t). • Fu¨r alle X ∈ BS, e ∈ T(S,BS), t ∈ TΣ(V )eX und u ∈ TΣ(V )X , g ∗(t(u)) = g ∗(t)(g ∗(u)). • Fu¨r alle e ∈ T(S,BS) t ∈ TΣ(V )2 und u, v ∈ TΣ(V )e, g ∗(ite(t, u, v)) = if g ∗(t) = 1 then g ∗(u) else g ∗(v). Daraus ergibt sich sofort die folgende induktive Definition von fold A, falls Σ konstruktiv ist: • Fu¨r • Fu¨r • Fu¨r • Fu¨r alle alle alle alle b ∈ B ∈ BS, fold A(b) = b. f : B → C ∈ BF und b ∈ B, fold A(f b) = f (b). c : 1 → e ∈ F , fold A(c) = cA. n > 0, f : e1 × · · · × en → e ∈ F und ti ∈ TΣ,ei , 1 ≤ i ≤ n, fold A(t1, . . . , tn) = (fold A(t1), . . . , fold A(tn)). 41 Beispiel Die Haskell-Funktion foldReg von Compiler.hs implementiert die Faltung fold A : TReg(CS) → A in einer beliebigen Reg(CS)-Algebra A. Aus der Eindeutigkeit von fold regBeh : TReg(CS) → regBeh und der Reg(CS)-Homomorphie ∗ von χ : P(X ∗) → 2X (s.o.) folgt sofort: fold regBeh = χ ◦ fold Lang . o Alle initialen Σ-Algebren sind Σ-isomorph und jede zu einer initialen Σ-Algebra isomorphe Σ-Algebra ist ebenfalls initial. Man spricht deshalb auch von der initialen Σ-Algebra und kann die Isomorphie zwischen ihr und einer gegebenen Σ-Algebra A zeigen, indem man beweist, dass auch A initial ist. Beispiel N ist die (Tr¨agermenge der) initiale(n) Nat-Algebra (siehe Beispiel 2.4). Beispiel X ∗ die (Tr¨agermenge der) initiale(n) List(X)-Algebra (siehe Beispiel 2.5). Aufgabe Zeigen Sie fold Bool = β Bro . 42 Aufgabe Zeigen Sie durch Induktion u¨ber den Aufbau von t, dass fu¨r alle t ∈ TReg(CS) ¨ die folgende Aquivalenz gilt: ∈ fold Lang (t) ⇔ fold Bool (t) = 1. Finale Algebren Sei Σ = (S, BS, BF, F ) eine beliebige Signatur. Eine Σ-Algebra A heißt final (oder terminal), wenn es zu jeder Σ-Algebra A genau einen Σ-Homomorphismus unfold A : A → Fin gibt. Alle finalen Σ-Algebren sind Σ-isomorph und jede zu einer finalen Σ-Algebra isomorphe Σ-Algebra ist ebenfalls final. Man spricht deshalb auch von der finalen Σ-Algebra und kann die Isomorphie zwischen ihr und einer gegebenen Σ-Algebra A zeigen, indem man beweist, dass auch A final ist. Ist Σ konstruktiv, dann ist die wie folgt definierte Σ-Algebra Fin Σ final: • Fu¨r alle s ∈ S, Fin Σ,s = 1. • Fu¨r alle f : e → s ∈ F und a ∈ Fin Σ,e, f Fin (a) = . 43 unfold A bildet jedes Element von A auf ab. Finale Algebren fu¨r konstruktive Signaturen sind demnach uninteressant. Anders sieht es bei destruktiven Signaturen aus. Deren finale Algebren bestehen aus Cotermen, die man sich als zu endlichen oder unendlichen B¨aumen entfaltete Graphen vorstellen kann (siehe Kapitel 20). Das begru¨ndet den Namen unfold A. So wie die Tr¨agermengen der initialen Σ-Algebra leer sind, wenn Σ keinen Konstruktur entha¨lt, dessen Domain aus Basismengen besteht, so hat die finale Σ-Algebra nur einelementige Tr¨agermengen, wenn Σ keinen Destruktur enth¨alt, dessen Range aus Basismengen besteht. Satz 2.9 Beh(X, Y ) (siehe 2.7) ist eine finale DAut(X, Y )-Algebra (siehe z.B. [29], Theorem FINAUT). o Sei A eine beliebige DAut(X, Y )-Algebra. Der eindeutige DAut(X, Y )-Homomorphismus unfold A von A nach Beh(X, Y ) ist wie folgt definiert: unfold A : A → Beh(X, Y ) a 7→ X A ∗ run (a) βA −→ Astate −→ Y, 44 ∗ wobei runA : A → AX δ A auf W¨orter fortsetzt: Fu¨r alle a ∈ Astate, x ∈ X und w ∈ X ∗, runA(a)() =def a, runA(a)(xw) =def runA(δ A(a)(x))(w). Demnach entspricht runA der Erreichbarkeitsfunktion von 2.8 mit vertauschten Argumenten: runA = flip(reachA). Satz 2.10 Pow (X) (siehe 2.7) ist eine finale Acc(X)-Algebra und unfold A : A → Pow (X) a 7→ {w ∈ X ∗ | β A(runA(a)(w)) = 1} ist der eindeutige Acc(X)-Homomorphismus von A nach Pow (X). ∗ Beweis. Die Behauptung folgt wegen der Acc(X)-Isomorphie zwischen 2X und P(X ∗) sofort aus Satz 2.9. o Aufgabe Zeigen Sie, dass unfold Pow : Pow (X) → Beh(X, 2) mit χ u¨bereinstimmt. ¨ Ubersicht u¨ber die oben definierten Reg(CS)- bzw. DAut(X, Y )-Algebren und ihre jeweiligen Implementierungen in Compiler.hs: 45 Siehe 2.1/2/6/7 Signatur Tr¨agermenge Reg(CS) regT RegT cs initial P(X ∗) DAut(X, Y ) Algebra TReg(CS) TReg(CS) Acc(X) Lang(X) Bro(CS) accBroz Pow (X) final χ(Pow (X)) 2X ∗ χ(Lang(X)) Beh(X, 2) regBeh accBeh final YX Beh(X, Y ) ∗ syms(CS) final + Regword (CS) regWord 2 Bool 46 2.11 Von Erkennern regul¨ arer Sprachen zu Compilern regul¨ arer Ausdru ¨ cke Die Faltung eines Reg(CS)-Grundterms in Lang(X) (siehe 2.7) nennt man die Sprache L(t) von t. Umgekehrt heißt eine Menge L von W¨ortern u¨ber X eine regul¨ are Sprache, wenn L die Sprache eines Reg(CS)-Grundterms ist. Angewendet auf einen Reg(CS)-Grundterm t, liefert fold regBeh = χ ◦ fold Lang einen Erkenner der Sprache von t: Angewendet auf ein Wort w ∈ X ∗, liefert fold regBeh(t) genau dann den Wert 1, wenn w zur Sprache von t geh¨ort. Damit erfu¨llt fold regBeh(t) dieselbe Aufgabe wie der klassische Potenzautomat, der u¨ber ¨ den Umweg eines nichtdeterministischen Automaten mit -Uberg ¨angen aus t gebildet wird (siehe Beispiel 12.3). 47 Wegen der Initialit¨at von TReg(CS) gibt es genau einen Reg(CS)-Homomorphismus fold Lang : TReg(CS) → Lang(X). Wegen der Finalit¨at von TReg(CS) (Satz 2.10) gibt es genau einen Acc(X)-Homomorphismus unfold Bro : Bro(CS) → Pow (X). ¨ Daraus ergeben sich folgende Aquivalenzen: fold Lang ist Acc(X)-homomorph ⇔ fold Lang = unfold Bro ⇔ unfold Bro ist Reg(CS)-homomorph. Damit ist neben fold regBeh(t) = χ(fold Lang (t)) auch χ(unfold Bro (t)) ein Erkenner der Sprache von t. ¨ Die Ubereinstimmung von fold Lang und unfold Bro kann auch aus der Form der Gleichungen, die den Brzozowski-Automaten definieren, geschlossen werden (siehe Beispiele 2.7 und 17.9). 48 Vorschaltung eines Parsers fu are Ausdru ¨ r regul¨ ¨ cke vor den Erkenner der jeweiligen regul¨ aren Sprache Im Folgenden werden wir einen Parser fu¨r die Wortdarstellung eines regul¨aren Ausdrucks t mit dessen Faltung in einer beliebigen Reg(CS)-Algebra A verknu¨pfen und damit ein erstes Beispiel fu¨r einen Compiler erhalten, der die Elemente einer Wortmenge ohne den Umweg u¨ber Syntaxb¨aume – hier: Reg(CS)-Terme – in Elemente einer Zielalgebra A – einer Reg(CS)-Algebra – u¨bersetzt. Werden regul¨are Ausdru¨cke als W¨orter (z.B. u¨ber syms(CS); siehe 2.6) eingelesen, dann muss dem Erkenner fold regBeh(t) eine Funktion parseREG : syms(CS)+ → 1 + TReg(CS) vorgeschaltet werden, die jedes Eingabewort w ∈ syms(CS)+, falls mo¨glich, in einen Reg(CS)-Term t mit π1(fold Regword (t)) = w u¨berfu¨hrt (siehe 2.6): parseREG ◦ π1 ◦ fold Regword = inc : TReg(CS) → 1 + TReg(CS). (1) Falls w keinem Reg(CS)-Term entspricht, soll w von parseREG auf abgebildet werden. REG steht hier fu¨r eine konkrete Syntax, d.h. eine kontextfreie Grammatik, deren Sprache mit dem Bild von TReg(CS) unter π1 ◦ fold Regword u¨bereinstimmt (siehe Beispiel 4.1). 49 Sei W eine Wortmenge. Wie in Kapitel 1 erwa¨hnt wurde und in Kapitel 5 pra¨zisiert werden wird, kann jede Funktion parseG, die jedes Wort w der Sprache L ⊆ W einer Grammatik in einen Syntaxbaum transformiert, zu einem generischen Compiler compileA G : W → 1+A erweitert werden, der w direkt in eine beliebige Algebra A der durch G bestimmten Signatur Σ(G) u¨bersetzt (siehe Kapitel 4). Der Compiler komponiert den Parser mit der Faltung von Syntaxb¨aumen in A: A compileA (2) G = (1 + fold ) ◦ parseG , wobei die Funktion (1 + fold A) : 1 + TΣ(G) → 1 + A auf und jeden Syntaxbaum t auf fold A(t) abbildet. Im Fall G = REG und W = syms(CS)+ stimmt compileA G (w) mit der Faltung des durch w dargestellten regul¨aren Ausdrucks t in der Reg(CS)-Algebra A u¨berein: Regword A compileA (t))) REG (w) = compileREG (π1 (fold (2) (1) = (1 + fold A)(parseREG(π1(fold Regword (t)))) = (1 + fold A)(inc(t)) = fold A(t). Wegen der Initialit¨at von TReg(CS) stimmt fold TReg(CS) mit der Identit¨at auf TReg(CS) u¨berein. Folglich ist parseREG die TReg(CS)-Instanz von compileREG: parseREG = id1+TReg(CS) ◦ parseREG = (1 + idTReg(CS) ) ◦ parseREG (2) T Reg(CS) = (1 + fold TReg(CS) ) ◦ parseREG = compileREG . 50 Im Haskell-Modul Compiler.hs ist compileREG durch die Funktion regToAlg implementiert. Die Parameter von regToAlg sind die gewu¨nschte Zielalgebra A – in Form einer Zahl zwischen 0 und 7 – sowie das jeweilige Eingabewort w. Im folgenden Diagramm sind die wichtigsten Beziehungen zwischen den behandelten Algebren und Homomorphismen zusammengefasst: parseREG 1 + fold Lang 1+χ syms(CS) 1 + TReg(CS) 1 + Lang(X) 1 + regBeh ≺ f f f = inc inc inc = = Regword π1 ◦ fold + ∪ ∪ Bool≺≺ fold Bool = β Bro TReg(CS) w w w w w w w w w Bro(CS) δ Bro g Bro(CS)X fold Lang = unfold Bro Lang(X) w w w w w w w w w Pow (X) δ Pow g Pow (X)X ∪ χ = χ regBeh w w w w w w w w w Beh(X, 2) δ Beh g Beh(X, 2)X 51 2.12 Initiale Automaten Sei A eine DAut(X, Y )-Algebra und a ∈ Astate. Die Menge hai = {runA(a)(w) | w ∈ X ∗} = {reachA(w)(a) | w ∈ X ∗} der Folgezust¨ ande von a in A ist die Tra¨germenge der kleinsten Unteralgebra von A (siehe Satz 3.2), die a enth¨alt (siehe Unter- und Bildalgebren). Das Paar (A, a) heißt initialer Automat. ∗ (A, a) realisiert eine Verhaltensfunktion f ∈ Y X , wenn das Bild von a unter dem eindeutigen DAut(X, Y )-Homomorphismus unfold A : A → Beh(X, Y ) mit f u¨bereinstimmt, kurz: unfold A(a) = f . Sei Y = 2. (A, a) erkennt oder akzeptiert eine Sprache L ⊆ X ∗, wenn das Bild von a unter dem eindeutigen Acc(X)-Homomorphismus unfold A : A → Pow (X) mit L u¨bereinstimmt, kurz: unfold A(a) = L. ∗ Wegen der Isomorphie zwischen P(X ∗) und 2X akzeptiert ein initialer Automat genau dann eine Sprache L ⊆ X ∗, wenn er die charakteristische Funktion χ(L) von L realisiert. 52 Wie zeigt man, dass initiale Automaten bestimmte Sprachen erkennen? Sei A eine Acc(X)-Algebra, {La ⊆ X ∗ | a ∈ Astate} und h : A → Pow (X) definiert durch h(a) = La fu¨r alle a ∈ Astate. Da Pow (X) eine finale Acc(X)-Algebra ist, erkennt fu¨r alle a ∈ Astate der initiale Automat (A, a) genau dann die Sprache La, wenn h Acc(X)homomorph ist. Aufgabe Wie zeigt man analog, dass initiale Automaten bestimmte Verhaltensfunktionen realisieren? Satz 2.13 (Minimale Automaten) Fu¨r alle f : X ∗ → Y ist (hf i, f ) eine minimale Realisierung von f . Beweis. Sei (A, a) ein initialer Automat mit f = unfold A(a) und h : hai → hf i die Einschr¨ankung von unfold A auf hai. Wir zeigen, dass h wohldefiniert und surjektiv ist und damit |hf i| ≤ |hai| ≤ |Astate| gilt. h ist wohldefiniert, d.h. fu¨r alle b ∈ hai ist unfold A(b) ∈ hf i: Sei b ∈ hai. Dann gibt es w ∈ X ∗ mit runA(a)(w) = b. Da unfold A DAut(X, Y )-homomorph ist, gilt unfold A(b) = unfold A(runA(a)(w)) = runBeh (unfold A(a))(w) ∈ hunfold A(a)i = hf i. 53 h ist surjektiv: Sei g ∈ hf i, d.h. es gibt w ∈ X ∗ mit runBeh (f )(w) = g. Da unfold A DAut(X, Y )-homomorph ist, folgt g = runBeh (f )(w) = runBeh (unfold A(a))(w) = unfold A(runA(a)(w)) = h(runA(a)(w)) aus runA(a)(w) ∈ hai. o 2.14 Baumautomaten In der Theorie formaler Sprachen kommen Algebren als Baumautomaten vor (siehe z.B. [30, 31, 39]): Sei Σ eine konstruktive Signatue. Ein Σ-Baumautomat (B, E) enth¨alt neben einer ΣAlgebra B eine S-sortige Teilmenge E = {Es ⊆ Bs | s ∈ S}, die als Menge der Endzust¨ ande von (B, E) bezeichnet wird. L(B, E) =def (fold B )−1(E) ⊆ TΣ heißt von (B, E) erkannte Baumsprache. Eine S-sortige Menge L von Σ-Grundtermen heißt regul¨ ar, wenn es einen endlichen ΣBaumautomaten (B, E) gibt, der L erkennt, fu¨r den also fold B (L) = E gilt. Diese Definition verallgemeinert den Regularit¨atsbegriff von W¨ortern u¨ber X – die eineindeutig List(X)-Termen entsprechen – auf Σ-Terme: 54 ∗ Sei h = fold X : TList(X) → X ∗. Zu jedem initialen Automaten (A, a) gibt es den List(X)-Baumautomaten (B, χ−1(β A)) mit Blist = Areg , nilB = a und consB = λ(x, a).δ A(a)(x), der h−1(unfold A(a)) erkennt. Umgekehrt gibt es zu jedem List(X)-Baumautomaten (B, E) den initialen Automaten (A, nilB ) mit Areg = Blist, δ A = λa.λx.consB (x, a) und β A = χ(E), der h(L(B, E)) erkennt. Eine Baumsprache L ⊆ TList(X) ist also genau dann regul¨ar, wenn es einen endlichen initialen Automaten (A, a) gibt, der h(L) erkennt, was wiederum – laut 3.13 oder 12.3 – genau dann gilt, wenn h(L) – im Sinne von 2.9 – regul¨ar ist. Baumautomaten werden zum Beispiel bei der Analyse von XML-Dokumenten eingesetzt (siehe Beispiel 4.3). 55 3 Rechnen mit Algebren In diesem Abschnitt werden die beiden wichtigsten einstelligen Algebratransformationen: die Bildung von Unteralgebren bzw. Quotienten, und ihr Zusammenhang zu Homomorphismen vorgestellt. Unteralgebren und Quotienten modellieren Restriktionen bzw. Abstraktionen eines gegebenen Modells. Beide Konstrukte sind aus der Mengenlehre bekannt. Sie werden hier auf S-sortige Mengen fortgesetzt. Unter- und Bildalgebren Sei Σ = (S, BS, BF, F ) eine Signatur und A eine Σ-Algebra. Eine Σ-Algebra B heißt Σ-Unteralgebra oder Σ-Invariante von A, wenn • fu¨r alle s ∈ S, Bs eine Teilmenge von As ist, • fu¨r alle f : e → e0 ∈ F und a ∈ Be f B (a) = f A(a) gilt. Auch alle zu B isomorphen Σ-Algebren werden als Unteralgebren von A bezeichnet. B heißt Invariante, weil fu¨r alle f : e → e0 ∈ F und a ∈ Be f A(a) ∈ Be0 gilt, die Zugeh¨origkeit von Elementen von A zu B also invariant gegenu¨ber der Anwendung von Funktionen f A, f ∈ F , ist. 56 Beispiel 3.1 S ∗ Sei X = CS. Die Teilmenge {fold Lang (t) | t ∈ TReg(CS)} von 2X bildet eine Unteralgebra von Lang(X). o Sei h : A → C ein Σ-Homomorphismus. Die Σ-Algebra img(h) mit • img(h)s = hs(As) = {h(a) | a ∈ As} fu¨r alle s ∈ S, • f img(h)(h(a)) = f C (h(a)) fu¨r alle f : e → e0 ∈ F und a ∈ Ae heißt Bild(algebra) von h. Satz 3.2 (Unteralgebren) (1) Sei B eine Unteralgebra von A. Die Inklusion incB : B → A, die jedes b ∈ B auf b abbildet, ist Σ-homomorph. (2) (Homomorphiesatz) Sei h : C → A ein Σ-Homomorphismus. img(h) ist eine ΣUnteralgebra von A. h0 : C → img(h) c 7→ h(c) ist ein surjektiver Σ-Homomorphismus. 57 Ist h injektiv, dann ist h0 bijektiv. Alle Σ-Homomorphismen h00 : C → img(h) mit incimg(h) ◦ h00 = h stimmen mit h0 u¨berein. h A C h0 incimg(h) img(h) (3) Sei Σ eine konstruktive Signatur und A eine Σ-Algebra. img(fold A) ist die kleinste Σ-Unteralgebra von A und TΣ ist die einzige Σ-Unteralgebra von TΣ. Demnach erfu¨llen alle zu TΣ isomorphen Σ-Algebren A das Induktionsprinzip, d.h. eine pr¨adikatenlogische Formel ϕ gilt fu¨r alle Elemente von A, wenn ϕ von allen Elementen einer Σ-Unteralgebra von A erfu¨llt wird. (4) Sei A eine Σ-Algebra und die A die einzige Σ-Unteralgebra von A. Dann gibt es fu¨r alle Σ-Algebren B ho¨chstens einen Σ-Homomorphismus h : A → B. Beweis von (3). Sei B eine Unteralgebra von A. Da TΣ initial und incB Σ-homomorph ist, kommutiert das folgende Diagramm: 58 fold A A TΣ fold = B incB B Daraus folgt fu¨r alle t ∈ TΣ, fold A(t) = incB (fold B (t)) = fold B (t) ∈ B. Also ist das Bild von fold A in B enthalten. Sei B eine Unteralgebra von TΣ. Da TΣ initial ist, folgt incB ◦fold B = idTΣ aus (1). Da idTΣ surjektiv ist, ist auch incB surjektiv. Da incB auch injektiv ist, sind B und TΣ isomorph. Also kann B nur mit TΣ u¨bereinstimmen. Beweis von (4). Seien g, h : A → B Σ-Homomorphismen. Dann ist C = {a ∈ A | g(a) = h(a)} eine Σ-Unteralgebra von A: Sei f : e → e0 ∈ F und a ∈ Ae mit ge(a) = he(a). Da g und h Σ-homomorph sind, gilt ge0 (f A(a)) = f B (ge(a)) = f B (he(a)) = he0 (f A(a)). Da g und h S-sortig sind, folgt f A(a) ∈ Ce. 59 Da A die einzige Σ-Unteralgebra von A ist, stimmt C mit A u¨berein, d.h. fu¨r alle a ∈ A gilt g(a) = h(a). Also ist g = h. o Beispiel 3.3 (siehe Beispiel 3.1) Nach Satz 3.2 (3) ist img(fold Lang ) die kleinste Unteralgebra von Lang(X). o Kongruenzen und Quotienten Sei Σ = (S, BS, BF, F ) eine Signatur, A, B Σ-Algebren und R = {Rs ⊆ As × Bs | s ∈ S} eine S-sortige Teilmenge von A × B. Wie nennen sie deshalb eine S-sortige Relation. R wird wie folgt zur T(S,BS)-sortigen Relation geliftet: Sei X ∈ BS und e, e1, . . . , en ∈ T(S,BS). • RX ⊆ X 2 ist die Diagonale von X 2, ∆X = {(x, x) | x ∈ X}. (Basismenge) • Re1×···×en ⊆ Ae1×···×en × Be1×···×en ist die Menge aller Paare von n-Tupeln (a1, . . . , an), (b1, . . . , bn) mit (ai, bi) ∈ Rei fu¨r alle 1 ≤ i ≤ n. (Produkt) • ReX ⊆ AeX × BeX ist die Menge aller Paare (f : X → Ae, g : X → Be) von Funktionen mit (f (x), g(x)) ∈ Re fu¨r alle x ∈ X. (Potenz) 60 Lemma RLIFT Fu¨r alle S-sortigen Funktionen g, h : A → B, e ∈ T(S,BS) und die S-sortige Relation R = {(g(a), h(a)) | a ∈ A} gilt: Re = {(ge(a), he(a)) | a ∈ Ae}. o R heißt mit Σ vertr¨ aglich oder kurz: Σ-Kongruenz, wenn fu¨r alle (a, b) ∈ A × B und f : e → e0 ∈ F gilt: (a, b) ∈ Re ⇒ (f A(a), f B (b)) ∈ Re0 . Sei A = B. Dann bezeichnet man R als Σ-Kongruenz auf A und die kleinste S-sortige ¨ ¨ Aquivalenzrelation, die R enth¨alt, als Aquivalenzabschluss Req von R. ¨ Aufgabe Zeigen Sie, dass der Aquivalenzabschluss einer Σ-Kongruenz wieder eine ΣKongruenz ist. o Sei R eine Σ-Kongruenz auf A. Die Σ-Algebra A/R mit • (A/R)s = {[a]R | a ∈ As} = {{b ∈ As | (a, b) ∈ Rseq } | a ∈ As} fu¨r alle s ∈ S, • f A/R ([a]R ) = [f A(a)]R fu¨r alle f : e → e0 ∈ F und a ∈ Ae heißt Quotient(enalgebra) nach R. 61 Satz 3.4 (Quotienten) (1) Sei A eine Σ-Algebra und R eine Σ-Kongruenz auf A. Die natu ¨ rliche Abbildung ¨ natR : A → A/R, die jedes Element a ∈ A auf seine Aquivalenzklasse [a]R abbildet, ist Σ-homomorph. (2) (Homomorphiesatz) Sei h : A → B ein Σ-Homomorphismus und ker(h) ⊆ A2 der Kern von h, d.h. ker(h) = {(a, b) | h(a) = h(b)}. ¨ ker(h) ist eine Σ-Kongruenz, die mit ihrem Aquivalenzabschluss u¨bereinstimmt. h0 : A/ker(h) → B [a]ker(h) 7→ h(a) ist ein injektiver Σ-Homomorphismus. Ist h surjektiv, dann ist h0 bijektiv. Alle Σ-Homomorphismen h00 : A/ker(h) → B mit h00 ◦ nat = h stimmen mit h0 u¨berein. h B A h0 natker(h) A/ker(h) 62 (3) Sei Σ eine destruktive Signatur, A eine Σ-Algebra und Fin die finale Σ-Algebra (s.o). Der – Verhaltenskongruenz von A genannte – Kern des eindeutigen Σ-Homomorphismus unfold A : A → Fin ist die gr¨oßte Σ-Kongruenz auf A und die Diagonale von Fin 2 die einzige Σ-Kongruenz R auf Fin mit R = Req . Demnach erfu¨llen alle zu Fin isomorphen Σ-Algebren A das Coinduktionsprinzip: Sei E eine Menge von Σ-Gleichungen t = u zwischen Σ-Termen u¨ber V , die denselben Typ haben. E gilt in A, wenn es eine Σ-Kongruenz R auf A gibt, die fu¨r alle t = u ∈ E und g : V → A das Paar (g ∗(t), g ∗(u)) enth¨alt. (4) Sei B eine Σ-Algebra und die Diagonale von B 2 die einzige Σ-Kongruenz R auf B mit R = Req . Dann gibt es fu¨r alle Σ-Algebren A h¨ochstens einen Σ-Homomorphismus h : A → B. Beweis von (3). Sei R eine Kongruenz auf A. Da Fin final und natR Σ-homomorph ist, kommutiert das folgende Diagramm: unfold A Fin A unfold A/R natR A/R 63 Daraus folgt fu¨r alle a, b ∈ A, (a, b) ∈ R ⇒ [a]R = [b]R ⇒ unfold A(a) = unfold A/R ([a]R ) = unfold A/R ([b]R ) = unfold A(b). Also ist R im Kern von unfold A enthalten. Sei R eine Kongruenz auf Fin mit R = Req . Da Fin final ist, folgt unfold Fin/R ◦natR = idFin aus (1). Da idFin injektiv ist, ist auch natR injektiv. Da natR auch surjektiv ist, sind Fin und Fin/R isomorph. Also kann R nur die Diagonale von Fin 2 sein. Beweis von (4). Seien g, h : A → B Σ-Homomorphismen. Dann ist R = {(g(a), h(a)) | a ∈ A} eine Σ-Kongruenz auf B: Sei f : e → e0 ∈ F , a ∈ Ae und (ge(a), he(a)) ∈ Re. Da g und h Σ-homomorph sind, gelten f B (ge(a)) = ge0 (f A(a)) und f B (he(a)) = he0 (f A(a)). Da g und h S-sortig sind, folgt (f B (ge(a)), f B (he(a))) = (ge0 (f A(a)), he0 (f A(a))) ∈ Re0 aus Lemma RLIFT. Da ∆B die einzige Σ-Kongruenz auf B ist, stimmt R mit ∆B u¨berein, d.h. fu¨r alle a ∈ A gilt g(a) = h(a). Also ist g = h. o 64 Automatenminimierung durch Quotientenbildung Eine minimale Realisierung einer Verhaltensfunktion f : X ∗ → Y erh¨alt man auch als Quotienten eines gegebenen initialen Automaten A: Im Beweis von Satz 2.13 wurde der DAut(X, Y )-Homomorphismus h : hai → hf i definiert. Wegen der Surjektivit¨at von h ist hai/ker(h) nach Satz 3.4 (2) DAut(X, Y )-isomorph zu hf i. Nach Definition von h ist ker(h) = ker(unfold A) ∩ hai2. Nach Satz 3.4 (3) ist ker(unfold A) die gr¨oßte Σ-Kongruenz auf A, also die gr¨oßte bin¨are Relation R auf A, die fu¨r alle a, b ∈ Astate folgende Bedingung erfu¨llt: (a, b) ∈ R ⇒ β A(a) = β A(b) ∧ ∀ w ∈ X ∗ : (δ A(a)(w), δ A(b)(w)) ∈ R. (1) Ist Astate endlich, dann l¨asst sich ker(unfold A) schnell und elegant mit dem in Abschnitt 2.3 von [24] und in [28] beschriebene (und in Haskell implementierte) Paull-UngerVerfahren berechnen. Es startet mit R0 = A2state und benutzt die Kontraposition (a, b) 6∈ R ⇐ β A(a) 6= β A(b) ∨ ∀ w ∈ X ∗ : (δ A(a)(w), δ A(b)(w)) 6∈ R (2) von (1), um R0 schrittweise auf den Kern von unfold A zu reduzieren. 65 Transitionsmonoid und syntaktische Kongruenz Das Bild der Mon-homomorphen Erreichbarkeitsfunktion reachA : X ∗ → (Astate → Astate) von A (siehe 2.8) heißt Transitionsmonoid von A. Satz 3.5 (Transitionsmonoide und endliche Automaten) Sei a ∈ Astate und R der Kern von reachhai : X ∗ → (hai → hai). hai ist genau dann ¨ endlich, wenn R endlich viele Aquivalenzklassen hat oder das Transitionsmonoid von hai endlich ist. Beweis. Nach Satz 3.4 (2) ist das Transitionsmonoid von hai Mon-isomorph zum Quotienten X ∗/R. Außerdem ist die Abbildung h : X ∗/R → hai [w]R 7→ δA∗(a)(w) wohldefiniert: Sei (v, w) ∈ R. Dann gilt reachhai(v) = reachhai(w), also insbesondere h([v]R ) = runA(a)(v) = δ hai∗(a)(v) = reachhai(v)(a) = reachhai(w)(a) = δ hai∗(a)(w) = runA(a)(w) = h([w]R ). 66 Da h surjektiv ist, liefert die Komposition h ◦ g mit dem Isomorphismus g : img(reachhai) → X ∗/R eine surjektive Abbildung von img(reachhai) nach hai. Also u¨bertra¨gt sich die Endlichkeit des Transitionsmonoids von hai auf hai selbst. Umgekehrt ist das Transitionsmonoid jedes endlichen Automaten A endlich, weil es eine Teilmenge von Astate → Astate ist. o Sei L ⊆ X ∗. Das Transitionsmonoid von hLi heißt syntaktisches Monoid und der Kern von reachhLi syntaktische Kongruenz von L. Letztere l¨asst sich als Menge aller Paare (v, w) ∈ X ∗ × X ∗ mit uvu0 ∈ L ⇔ uwu0 ∈ L fu¨r alle u, u0 ∈ X ∗ charakterisieren. Satz 3.5 impliziert, dass sie genau dann endlich viele ¨ Aquivalenzklassen hat, wenn L von einem endlichen initialen Automaten (A, a) erkannt wird, was wiederum zur Regularit¨at von L ¨aquivalent ist (siehe 3.13 fu¨r einen direkten Beweis oder Beispiel 12.3 fu¨r den klassischen u¨ber die Konstruktion eines nichtdeterministischen Akzeptors von L). Folglich kann die Nichtregularit¨at einer Sprache L oft gezeigt werden, indem aus der Endlichkeit der Zustandsmenge eines Akzeptors von L ein Widerspruch hergeleitet wird. W¨are z.B. L = {xny n | n ∈ N} regul¨ar, dann g¨abe es einen endlichen Automaten (A, a) mit unfold A(a) = L. 67 Demnach mu¨sste fu¨r alle n ∈ N ein Zustand bn ∈ Areg existieren mit runA(a)(xn) = bn und β A(runA(bn)(y n)) = 1. Da A endlich ist, g¨abe es i, j ∈ N mit i 6= j und bi = bj . Daraus wu¨rde jedoch unfold A(xiy j ) = β A(runA(a)(xiy j )) = β A(runA(runA(a)(xi))(y j )) = β A(runA(bi)(y j )) = β A(runA(bj )(y j )) = 1 folgen, im Widerspruch dazu, dass xiy j nicht zu L geh¨ort. Also ist L nicht regul¨ar. 3.6 Termsubstitution Sei Σ = (S, BS, BF, F ) eine Signatur. Belegungen von Variablen durch Σ-Terme (u¨ber V ) heißen Substitutionen und werden u¨blicherweise mit kleinen griechischen Buchstaben bezeichnet (σ, τ, . . . ). Im Gegensatz zu den Belegungen von Kapitel 2 beschr¨anken wir den Wertebereich von Substitutionen σ : V → TΣ(V ) nicht auf die Tr¨agermenge der Algebra TΣ(V ), also auf Terme ohne λ und ite. Um ungewollte Bindungen von Variablen zu vermeiden, muss die Fortsetzung σ ∗ : TΣ(V ) → TΣ(V ) von σ auf Terme anders als die Auswertung g ∗ : TΣ(V ) → A einer Belegung g : V → A von Kapitel 2 definiert werden: 68 • Fu¨r alle X ∈ ( BS, e ∈ T(S,BS), x ∈ VX und t ∈ TΣ(V )e, λx0.σ[x0/x]∗(t) falls x ∈ var(σ(free(t) \ {x})), ∗ σ (λx.t) = λx.σ[x/x]∗(t) sonst. • Fu¨r alle e ∈ T(S,BS) t ∈ TΣ(V )2 und u, v ∈ TΣ(V )e, σ ∗(ite(t, u, v)) = ite(σ ∗(t), σ ∗(u), σ ∗(v)). In den restlichen F¨allen ist σ ∗ genauso definiert wie g ∗: • Fu¨r alle x ∈ V , σ ∗(x) = σ(x). • Fu¨r alle x ∈ X ∈ BS, σ ∗(x) = x. • Fu¨r alle n > 1 und t1, . . . , tn ∈ TΣ(V ), σ ∗(t1, . . . , tn) = (σ ∗(t1), . . . , σ ∗(tn)). • Fu¨r alle f : e → e0 ∈ F und t ∈ TΣ(V )e, σ ∗(f t) = f σ ∗(t). • Fu¨r alle X ∈ BS, e ∈ T(S,BS), t ∈ TΣ(V )eX und u ∈ TΣ(V )X , σ ∗(t(u)) = σ ∗(t)(σ ∗(u)). σ ∗(t) heißt σ-Instanz von t und Grundinstanz, falls σ alle Variablen von t auf Grundterme abbildet. Man schreibt h¨aufig tσ anstelle von σ ∗(t) sowie {t1/x1, . . . , tn/xn} fu¨r die Substitution σ mit σ(xi) = ti fu¨r alle 1 ≤ i ≤ n und σ(x) = x fu¨r alle x ∈ V \ {x1, . . . , xn}. 69 Satz 3.7 (Auswertung und Substitution) (1) Fu¨r alle Belegungen g : V → A und Σ-Homomorphismen h : A → B gilt: (h ◦ g)∗ = h ◦ g ∗. (2) Fu¨r alle Substitutionen σ, τ : V → TΣ(V ) gilt: (σ ∗ ◦ τ )∗ = σ ∗ ◦ τ ∗. Beweis. (1) Induktion u¨ber den Aufbau von Σ-Termen. (2) folgt aus (1), weil σ ∗ ein ΣHomomorphismus ist. o Term¨ aquivalenz und Normalformen Sei Σ = (S, BS, BF, F ) eine konstruktive Signatur. In diesem Abschnitt geht es um ΣAlgebren A, die eine gegebene Menge E von Σ-Gleichungen (s.o.) erfu ¨ llen, d.h. fu¨r die Folgendes gilt: • fu¨r alle t = t0 ∈ E und g : V → A gilt g ∗(t) = g ∗(t0). AlgΣ,E bezeichnet die Klasse aller Σ-Algebren, die E erfu¨llen. 70 ¨ Die E-Aquivalenz ≡E ist definiert als kleinste reflexive, symmetrische und transitive Σ-Kongruenz auf TΣ(X), die E enth¨alt, d.h. fu¨r alle t, t0 ∈ TΣ(V ) gilt: t = t0 ∈ E ⇒ t ≡E t0, (1) und unter Instanziierung abgeschlossen ist, d.h. fu¨r alle t, t0 ∈ TΣ(V ) und σ : X → TΣ(X) gilt: t ≡E t0 ⇒ tσ ≡E t0σ. (2) Aufgabe Zeigen Sie, dass der Quotient TΣ(V )/≡E E erfu¨llt. o Satz 3.8 Fu¨r alle Belegungen g : V → A in eine Σ-Algebra A, die E erfu¨llt, faktorisiert g ∗ : TΣ(V ) → A durch TΣ(V )/≡E , d.h. es gibt einen Σ-Homomorphismus h : TΣ(V )/≡E → A mit h ◦ nat≡E = g ∗. nat≡E incV TΣ(V ) TΣ(V )/≡E V g g ∗ (3) h g≺ A 71 Beweis. Da der Kern von g ∗ E entha¨lt und reflexiv, symmetrisch, transitiv, mit Σ vertr¨aglich sowie unter Instanziierung abgeschlossen ist (siehe [26], Satz GLK), ≡E aber die kleinste derartige Relation auf TΣ(V ) ist, ist letztere im Kern von g ∗ enthalten. Folglich ist h : TΣ(V )/≡E → A mit h([t]≡E ) = h(t) fu¨r alle t ∈ TΣ(V ) wohldefiniert. (3) kommutiert, was zusammen mit der Surjektivit¨at und Σ-Homomorphie von nat≡E impliziert, dass auch h Σ-homomorph ist. o Die durch den gestrichelten Pfeil angedeutete Eigenschaft von h, der einzige Σ-Homomorphismus zu sein, der (3) kommutativ macht, folgt ebenfalls aus der Surjektivita¨t und Σ-Homomorphie von nat≡E . Zusammen mit TΣ/≡E ∈ AlgΣ,E impliziert Satz 3.8, dass TΣ/≡E initial in AlgΣ,E ist, dass es also fu¨r alle A ∈ AlgΣ,E genau einen Σ-Homomorphismus h : TΣ/≡E → A gibt. Da g ∗ im Fall V = ∅ mit fold A u¨bereinstimmt, reduziert sich das obige Diagramm zu folgendem: nat≡E A TΣ fold A (3) h TΣ/≡E 72 Beispiel 3.9 Sei Σ = Mon, x, y, z ∈ V und E = {mul(one, x) = x, mul(x, one) = x, mul(mul(x, y), z) = mul(x, mul(y, z))}. Die freie Mon-Algebra TMon (V ) ist kein Monoid, wohl aber ihr Quotient TMon (V )/ ≡E . Man nennt ihn das freie Monoid (u¨ber V ). Aufgabe Zeigen Sie, dass das freie Monoid tats¨achlich ein Monoid ist, also E erfu¨llt, und Mon-isomorph zu V ∗ ist. o Von ihrer Definition her sind Quotienten wenig anschaulich (und schlecht implementierbar), ¨ weil sie aus Aquivalenzklassen, also Mengen, bestehen. Deshalb werden isomorphe Darstel¨ lungen wie z.B. V ∗ in Beispiel 3.9 bevorzugt, deren Elemente die Aquivalenzklassen nur eindeutig repr¨asentieren. Wegen der Strukturgleichheit isomorpher Algebren werden sie dann auch als Quotienten bezeichnet, z.B. V ∗ als Quotient von TMon (V ). Bei der Berechnung a¨quivalenter Normalformen beschra¨nkt man sich meist auf die folgende Teilrelation von ≡E , die nur “orientierte” Anwendungen der Gleichungen von E zul¨asst: Die E-Reduktionsrelation →E besteht aus allen Paaren (u{t/x}, u{t0/x}) mit t = t0 ∈ E, u ∈ TΣ(X) und σ : X → TΣ(X). 73 + Die kleinste transitive Relation auf TΣ(X), die →E enth¨alt, wird mit →E bezeichnet. + Aufgabe Zeigen Sie, dass →E die kleinste transitive, mit Σ vertr¨agliche und unter Instanziierung abgeschlossene Relation auf TΣ(V ) ist, die E enth¨alt. Folgern Sie daraus, dass + →E eine Teilmenge von ≡E ist. o + Sei t ∈ TΣ(V ). u ∈ TΣ(V ) heißt E-Normalform von t, wenn t →E u gilt und u zu einer vorgegebenen Teilmenge von TΣ(V ) geh¨ort. Eine Funktion reduce : TΣ(V ) → TΣ(V ) heißt E-Reduktionsfunktion, wenn fu¨r alle t ∈ TΣ(V ), reduce(t) eine E-Normalform von t ist. Sei A ∈ AlgΣ,E und g : V → A. Wegen + →E ⊆ ≡E ⊆ ker(g ∗) (siehe obige Aufgabe und den Beweis von Satz 3.8) gilt g ∗(t) = g ∗(reduce(t)) fu¨r alle t ∈ TΣ(V ), also kurz: g ∗ ◦ reduce = g ∗. (3) Allgemeine Methoden zur Berechnung von Normalformen werden in [26], §5.2 behandelt. 74 Beispiel 3.10 Normalformen regul¨ arer Ausdru ¨ cke E bestehe aus folgenden Reg(CS)-Gleichungen: f (f (x, y), z) = f (x, f (y, z)) par(x, y) = par(y, x) (Assoziativita¨t von f ∈ {par, seq}) (Kommutativit¨at von par) seq(x, par(y, z)) = par(seq(x, y), seq(x, z)) (Linksdistributivit¨at von seq u¨ber par) seq(par(x, y), z) = par(seq(x, z), seq(y, z)) (Rechtsdistributivit¨at von seq u¨ber par) par(x, x) = x (Idempotenz von par) par(mt, x) = x par(x, mt) = x (Neutralit¨at von mt bzgl. par) seq(eps, x) = x seq(x, eps) = x (Neutralit¨at von eps bzgl. seq) seq(mt, x) = mt seq(x, mt) = mt (Annihilation) f (ite(x, y, z), z 0) = ite(x, f (y, z 0), f (z, z 0)) (f ∈ {par, seq}) f (z 0, ite(x, y, z)) = ite(x, f (z 0, y), f (z 0, z)) (f ∈ {par, seq}) Demnach entfernt eine E-Reduktionsfunktion mt und Mehrfachkopien von Summanden aus Summen sowie eps aus Produkten, ersetzt alle Produkte, die mt enthalten, durch mt, distribuiert seq u¨ber par und linearisiert geschachtelte Summen und Produkte rechtsassoziativ. Angewendet auf Reg(CS)-Grundterme entspricht sie deren Faltung in der Reg(CS)Algebra regNorm (siehe Compiler.hs). S Sei X = CS. Da Lang(X) E erfu¨llt, gilt (3) fu¨r A = Lang(X). 75 Algebren, die E erfu¨llen, heißen idempotente Semiringe. Da E weder den Sternoperator iter : reg → reg noch die Konstanten C : 1 → reg enth¨alt, brauchen diese in einem Semiring nicht definiert zu sein. Auch die Idempotenz von par ist keine Anforderung an Semiringe. Kleene-Algebren sind Semiringe, auf denen ein Sternoperator definiert ist und die neben E weitere (den Sternoperator betreffende) Gleichungen erfu¨llen. Die Elemente von Beh(X, Y ) (siehe 2.7) heißen formale Potenzreihen, wenn Y ein Semiring ist (siehe [34], Kapitel 9). 3.11 Die Brzozowski-Gleichungen Die folgende Menge BRE von – Brzozowski-Gleichungen genannten – Reg(CS)Gleichungen hat in TReg(CS) genau eine L¨osung, d.h. es gibt genau eine Erweiterung von TReg(CS) zur Acc(X)-Algebra, die BRE erfu¨llt (siehe Beispiel 17.4). In Abschnitt 2.7 wurde diese L¨osung unter dem Namen Bro(CS) eingefu¨hrt. Existenz und Eindeutigkeit folgen aus Satz 17.1 und werden dort aus dem in Satz 3.2 (3) eingefu¨hrten Induktionsprinzip abgeleitet. δ(eps) = λx.mt δ(mt) = λx.mt δ(C) = λx.ite(x ∈ C, eps, mt) 76 δ(par(t, u)) = λx.par(δ(t)(x), δ(u)(x)) δ(seq(t, u)) = λx.par(seq(δ(t)(x), u), ite(β(t), δ(u)(x), mt)), δ(iter(t)) = λx.seq(δ(t)(x), iter(t)) β(eps) = 1 β(mt) = 0 β(C) = 0 β(par(t, u)) = max{β(t), β(u)} β(seq(t, u)) = β(t) ∗ β(u) β(iter(t)) = 1 t und u sind hier Variablen der Sorte reg. Gem¨aß der Assoziation von δ(t) mit der Ableitung von t (siehe 2.7) werden die Brzozowski-Gleichungen von BRE auch Differentialgleichungen genannt. Nach obiger Lesart definiert BRE Destruktoren (δ und β) auf der Basis von Konstruktoren (den Operationen von Reg(CS)). Umgekehrt la¨sst sich BRE auch als Definition dieser Konstruktoren auf der Basis von δ und β auffassen: Nach Satz 17.7 hat BRE n¨amlich auch in der finalen Acc(X)-Algebra Pow (X) genau eine L¨osung, d.h. es gibt genau eine Erweiterung von Lang(X) zur Reg(CS)-Algebra, die BRE erfu¨llt (siehe Beispiel 17.9). o 77 3.12 Aus Normalformen gebildete Erkenner regul¨ arer Sprachen Der Erkenner χ(unfold Bro (t)) regul¨arer Sprachen (siehe Abschnitt 2.11) ben¨otigt viel Platz, weil die wiederholten Aufrufe von δ Bro aus t immer gr¨oßere Ausdru¨cke erzeugen. Um das zu vermeiden, ersetzen wir Bro(CS) durch die Acc(X)-Algebra Norm(CS) (accNorm in Compiler.hs), die bis auf die Interpretation von δ mit Bro(CS) u¨bereinstimmt. δ Norm normalisiert die von δ Bro berechneten Folgezust¨ande mit der in Beispiel 3.10 beschriebenen Reduktionsfunktion reduce : TReg(CS)(V ) → TReg(CS)(V ). Fu¨r alle t ∈ TReg(CS), δ Norm (t) =def reduce ◦ δ Bro (t). (1) fold Lang ◦ reduce = fold Lang . (2) Gem¨aß Beispiel 3.10 gilt Es bleibt zu zeigen, dass Bro(CS) und Norm(CS) dieselben Sprachen erkennen, dass fu¨r alle t ∈ TReg(CS) gilt: unfold Norm (t) = unfold Bro (t). (3) Wir beweisen (3) durch Induktion u¨ber die L¨ange der W¨orter u¨ber X. β Norm (runNorm (t)()) = β Norm (t) = β Bro (t) = β Bro (runBro (t)()). 78 Fu¨r alle x ∈ X und w ∈ X ∗, β Norm (runNorm (t)(xw)) = β Norm (runNorm (δ Norm (t)(x))(w)) = unfold Norm (δ Norm (t)(x))(w) Induktionsvor . = unfold Bro (δ Norm (t)(x))(w) (1) = unfold Bro (reduce(δ Bro (t))(x))(w) = fold Lang (reduce(δ Bro (t))(x))(w) (2) (1) = fold Lang (δ Bro (t)(x))(w) = unfold Bro (δ Bro (t)(x))(w) = β Bro (runBro (δ Bro (t)(x))(w)) = β Bro (runBro (t)(xw)). Also gilt (3): unfold Norm (t) = {w ∈ X ∗ | β Norm (runNorm (t)(w)) = 1} = {w ∈ X ∗ | β Bro (runBro (t)(w)) = 1} = unfold Bro (t). 3.13 Erkenner regul¨ arer Sprachen sind endlich Aus der Gu¨ltigkeit von BRE in Pow (X) lassen sich die folgenden Gleichungen fu¨r runPow : P(X ∗) → P(X ∗)X ∗ ableiten (siehe 2.9): Fu¨r alle w ∈ X ∗, C ∈ CS und L, L0 ⊆ P(X ∗), ( 1 falls w = , runPow (epsLang )(w) = (4) ∅ sonst, runPow (mtLang )(w) = ∅, (5) 79 runPow (C Lang )(w) = C falls w = , 1 falls w ∈ C, ∅ sonst, (6) runPow (parLang (L, L0))(w) = runPow (L)(w) ∪ runPow (L0)(w), (7) runPow (seq Lang (L, L0))(w) = {uv | u ∈ runPow (L)(w), v ∈ L0} S ∪ uv=w (if ∈ runPow (L)(u) then runPow (L0)(v) else ∅), runPow (iterLang (L))(w) (8) = {uv | u ∈ runPow (L)(w), v ∈ iterPow (L)} S T ∪ u1...unv=w (if ∈ ni=1 runPow (L)(ui) then {uv 0 | u ∈ runPow (L)(v), v 0 ∈ iterPow (L)} else ∅) (9) (siehe z.B. [34], Theorem 10.1). Wir erinnern an Satz 2.13, aus dem folgt, dass fu¨r alle L ⊆ X ∗ der Unterautomat (hLi, L) von (Pow (X), L) ein minimaler Erkenner von L ist. Ist L die Sprache eines regul¨aren Ausdrucks t, ist also L = fold Lang (t), dann ist hLi = {runPow (fold Lang (t))(w) | w ∈ X ∗}. 80 Daraus folgt durch Induktion u¨ber den Aufbau von t, dass hLi endlich ist: Im Fall t ∈ {eps, mt} ∪ {C | C ∈ CS} besteht hLi wegen (4), (5) und (6) aus zwei, einem bzw. drei Zust¨anden. Im Fall t = par(t0, t00) folgt |hLi| ≤ |hfold Lang (t0)i| ∗ |hfold Lang (t00)i| aus (7). Im Fall t = seq(t0, t00) folgt |hLi| ≤ |hfold Lang (t0)i| ∗ 2|hfold Im Fall t = iter(t0) folgt |hLi| ≤ 2|hfold Lang (t0 )i| Lang (t00 )i| aus (8). aus (9). Damit ist ohne den u¨blichen Umweg u¨ber Potenzautomaten (siehe Beispiel 12.3) gezeigt, dass regul¨are Sprachen von endlichen Automaten erkannt werden. 81 4 Kontextfreie Grammatiken (CFGs) Sie ordnen einer konstruktiven Signatur Σ eine konkrete Syntax und damit eine vom Compiler verstehbare Quellsprache zu, so dass er diese in eine als Σ-Algebra formulierte Zielsprache u¨bersetzen kann. Auch wenn das zu die Quellsprache bereits als konstruktive Signatur Σ und die Zielsprache als Σ-Algebra gegeben sind, ben¨otigt der Compiler eine kontextfreie Grammatik, um Zeichenfolgen in Elemente der Algebra zu u¨bersetzen. Eine kontextfreie Grammatik (CFG) (mit Basismengen) G = (S, BS, Z, R) besteht aus • einer endlichen Menge S von Sorten (wie denen einer Signatur), die hier auch Nichtterminale oder Variablen genannt werden, • einer endlichen Menge BS nichtleerer (und i.d.R. mehrelementiger) Basismengen, • einer endlichen Menge Z von Terminal(symbol)en, • einer endlichen Menge R von Regeln s → w mit s ∈ S und w ∈ (S ∪ BS ∪ Z)∗, die auch Produktionen genannt werden. n > 0 Regeln s → w1, . . . , s → wn mit derselben linken Seite s werden oft zu der einen Regel s → w1| . . . |wn zusammengefasst. 82 Beispiel 4.1 Hier kommt die in Abschnitt 2.11 erwa¨hnte CFG zur Wortdarstellung regul¨arer Ausdru¨cke. Sei CS eine endliche Menge nichtleerer Konstantenmengen (siehe 2.1), S = {reg}, BS = {C ∈ CS | |C| > 1}, Z = {, ∅, +,∗ , (, )} ∪ {c | {c} ∈ CS}, R = {reg → , reg → ∅, reg → reg + reg, reg → reg reg, reg → reg ∗, reg → (reg)} ∪ {reg → c | {c} ∈ CS} ∪ {reg → C | C ∈ BS}. REG = (S, BS, Z, R) ist eine CFG. o Oft genu¨gt es, bei der Definition einer CFG nur deren Regeln und Basismengen anzugeben. Automatisch bilden dann die Symbole, die auf der linken Seite einer Regel vorkommen, die Menge S der Sorten, w¨ahrend alle anderen W¨orter und Symbole (außer dem senkrechten Strich |; s.o.) auf der rechten Seite einer Regel Terminale oder Namen von Basismengen sind. Beispiel 4.2 Die Regeln der CFG JavaLight fu¨r imperative Programme mit Konditionalen und Schleifen lauten wie folgt: 83 Commands → Command → Sum Sumsect Prod Prodsect Factor Disjunct Conjunct Literal → → → → → → → → Command Commands | Command {Commands} | String = Sum; | if Disjunct Command else Command | if Disjunct Command | while Disjunct Command Prod Sumsect {+, −} Prod Sumsect | Factor Prodsect {∗, /} Factor Prodsect | Z | String | (Sum) Conjunct || Disjunct | Conjunct Literal && Conjunct | Literal !Literal | Sum Rel Sum | 2 | (Disjunct) String, {∗, /}, Z, Rel und 2 sind also die Basismengen von JavaLight. String bezeichnet die Menge aller Zeichenfolgen außer den in Regeln von JavaLight vorkommenden Symbolen und W¨ortern außer String. Rel bezeichnet eine Menge nicht n¨aher spezifizierter bina¨rer Relationen auf Z. Ein aus der Sorte Commands ableitbares JavaLightProgramm ist z.B. fact = 1; while x > 1 {fact = fact*x; x = x-1;} Die Wahl der Sorten von JavaLight wird sp¨ater n¨aher begru¨ndet. o 84 Beispiel 4.3 XMLstore (siehe [18], Abschnitt 2) Store → Orders Order P erson Emails Email Items Item Stock ItemS Suppliers Id → → → → → → → → → → → hstorei hstocki Stock h/stocki h/storei | hstorei Orders hstocki Stock h/stocki h/storei Order Orders | Order horderi hcustomeri P erson h/customeri Items h/orderi hnamei String h/namei | hnamei String h/namei Emails Email Emails | hemaili String h/emaili Item Items | Item hitemi Id hpricei String h/pricei h/itemi ItemS Stock | ItemS hitemi Id hquantityi Z h/quantityi Suppliers h/itemi hsupplieri P erson h/supplieri | Stock hidi String h/idi 85 Die Sprache von XMLstore beschreibt XML-Dokumente wie z.B. das folgende: <store> <order> <customer> <name> John Mitchell </name> <email> j.mitchell@yahoo.com </email> </customer> <item> <id> I18F </id> <price> 100 </price> </item> </order> <stock> <item> <id> IG8 </id> <quantity> 10 </quantity> <supplier> <name> Al Jones </name> <email> a.j@gmail.com </email> <email> a.j@dot.com </email> </supplier> </item> <item> <id> J38H </id> <quantity> 30 </quantity> <item> <id> J38H1 </id> <quantity> 10 </quantity> <supplier> <name> Richard Bird </name> </supplier> </item> <item> <id> J38H2 </id> <quantity> 20 </quantity> <supplier> <name> Mick Taylor </name> </supplier> </item> </item> </stock> </store> 86 Nicht-linksrekursive CFGs Sei G = (S, BS, Z, R) eine CFG und X = Z ∪ S BS. X ist die Menge der Eingabesymbole, die Compiler fu¨r G verarbeiten, w¨ahrend S ∪ BS ∪ Z die Menge der in R (neben → und manchmal auch |) vorkommenden Symbole ist. Der Unterschied betrifft vor allem die Basismengen B ∈ BS: W¨ahrend die Elemente von B zu X geh¨oren, taucht B in R nur als Menge auf. Die klassische Definition einer linksrekursiven Grammatik verwendet die (Links-)Ableitungsrelation →G = {(vsw, vαw) | s → α ∈ R, v, w ∈ (S ∪ BS ∪ Z)∗}. + ∗ →G und →G bezeichnen den transitiven bzw. reflexiv-transitiven Abschluss von →G. + G ist linksrekursiv, falls es s ∈ S und w ∈ (S ∪ BS ∪ Z)∗ mit s →G sw gibt, positiv + – aber umst¨andlicher – formuliert: wenn es zu jeder Ableitung s →G v eine Ableitung ∗ v →G w mit w = oder w ∈ (BS ∪ Z) × (S ∪ BS ∪ Z)∗ gibt. Z.B. ist REG linksrekursiv, JavaLight und XMLstore jedoch nicht (siehe Beispiele 4.1-4.3). 87 Linksassoziative Auswertung erzwingt keine Linksrekursion Nicht-linksrekursive Regeln fu¨r bin¨are Operationen bewirken, dass vom Parser erzeugte Syntaxb¨aume fu¨r Ausdru¨cke mit diesen Operationen rechtsassoziativ ausgewertet werden. Das ist aber nicht die u¨bliche Auswertungsreihenfolge, die man fu¨r ungeklammerte Ausdru¨cke wie x + y − z oder x/y ∗ z voraussetzt. Entha¨lt ein Ausdruck nur assoziative Operationen wie Addition, Multiplikation, Konjunktion und Disjunktion, dann kann man sich ruhig eine linksassoziative Faltung vorstellen, auch wenn er tats¨achlich rechtsassoziativ ausgewertet wird, weil jede Auswertungsreihenfolge zum gleichen Ergebnis fu¨hrt. Subtraktion und Division sind aber nicht assoziativ, so dass man fu¨r solche Operationen einen anderen Weg gehen muss, um mit nicht-linksrekursiven Regeln eine linksassoziative Faltung zu erreichen. Zu diesem Zweck enth¨alt JavaLight die Sorten Sumsect und Prodsect. Ihre Namen erinnern an ihre sp¨atere Interpretation als Mengen von Sektionen, also Funktionen wie z.B. (∗5) : Z → Z. Angewendet auf eine Zahl x, liefert (∗5) das Fu¨nffache von x. Die Sektionssorten von JavaLight wu¨rden automatisch eingefu¨hrt werden, wenn man die folgende Entrekursivierung auf eine linksrekursive Variante der Grammatik anwendet. 88 Verfahren zur Eliminierung von Linksrekursion Sei G = (S, BS, Z, R) eine CFG und S = {s1, . . . , sn}. Fu¨hre fu¨r alle 1 ≤ i ≤ n die beiden folgenden Schritte in der angegebenen Reihenfolge durch: • Fu¨r alle 1 ≤ j < i und Regelpaare (si → sj v, sj → w) ersetze die Regel si → sj v durch die neue Regel si → wv. • Falls vorhanden, streiche die Regel si → si. • Fu¨r alle Regelpaare (si → siw, si → ev) mit e ∈ (S ∪ BS ∪ Z) \ {si} ersetze die Regel o si → siw durch die drei neuen Regeln si → evs0i, s0i → ws0i und s0i → w. Die Verwendung von jeweils drei Sorten fu¨r arithmetische bzw. Boolesche Ausdru¨cke (Sum, Prod und Factor bzw. Disjunct, Conjunct und Literal ) bewirkt ebenfalls eine bestimmte Auswertungsreihenfolge und zwar diejenige, die sich aus den u¨blichen Priorit¨aten arithmetischer bzw. Boolescher Operationen ergibt. 89 Abstrakte Syntax Sei G = (S, BS, Z, R) eine CFG. Die konstruktive Signatur Σ(G) = (S, BS, ∅, F ) mit F = {fr : ei1 × . . . × eik → s | r = (s → e1 . . . en) ∈ R, {i1, . . . , ik } = {1 ≤ i ≤ n | ei ∈ S ∪ BS} heißt abstrakte Syntax von G. fr nennen wir Konstruktor von r. Beispiel 4.4 Die Signatur Reg(CS) ist die abstrakte Syntax der CFG REG von Beispiel 4.1. o Da jedes nullstellige Produkt mit der Menge 1 gleichgesetzt wird, hat der Konstruktor fr im Fall {1 ≤ i ≤ n | ei ∈ S ∪ BS} = ∅, in dem alle e1, . . . , en Terminale sind, den Typ 1 → s. Die Funktionssymbole von Σ(G) lassen sich i.d.R. direkt aus den Terminalen von G basteln. So wird manchmal fu¨r den aus der Regel r = (s → w0e1w1 . . . enwn) mit wi ∈ Z ∗ entstandenen Konstruktor fr die (Mixfix-)Darstellung w0 w1 . . . wn gew¨ahlt. 90 Umgekehrt kann jede konstruktive Signatur Σ = (S, BS, ∅, F ) in eine CFG G(Σ) = (S, BS, F ∪ {(, ), , }, R) mit Σ(G(Σ)) = Σ u¨berfu¨hrt werden, wobei R = {s → f (e1, . . . , en) | f : e1 × · · · × en → s ∈ F }. Σ(G)-Grundterme werden auch Syntaxb¨ aume von G genannt. Sie du¨rfen nicht mit Ableitungsb¨aumen verwechselt werden: Die Knoten eines Syntaxbaums sind mit Funktionssymbolen markiert, die Knoten eines Ableitungsbaums jedoch mit Sorten; Basismengen oder Terminalen (s.u.). Beispiel 4.5 SAB Die Grammatik SAB besteht aus den Sorten S, A, B, den Terminalen a, b und den Regeln r1 = S → aB, r4 = A → aS, r2 = S → bA, r5 = A → bAA, r3 = S → , r6 = B → bS, r7 = B → aBB. Demnach lauten die Konstruktoren der abstrakten Syntax von SAB wie folgt: f1 : B → S, f4 : S → A, f2 : A → S, f5 : A × A → A, f3 : 1 → S, f6 : S → B, f7 : B × B → B. 91 f1 f7 f6 f6 f1 f3 f6 ε f3 ε Syntaxbaum des Eingabewortes aababb Mit markierte Bl¨atter eines Syntaxbaums werden ku¨nftig weglassen. Die folgende SAB-Algebra SABcount berechnet die Anzahl #a(w) bzw. #b(w) der Vorkommen von a bzw. b im Eingabewort w: SABcount S = SABcount A = SABcount B = N2, f1SABcount = f4SABcount f2SABcount = f6SABcount f5SABcount f7SABcount = = = = λ(i, j).(i + 1, j), λ(i, j).(i, j + 1), λ((i, j), (k, l)).(i + k, j + l + 1), λ((i, j), (k, l)).(i + k + 1, j + l). o 92 Beispiel 4.6 Σ(JavaLight) = { Commands, Command , Sum, Sumsect, Prod , Prodsect, Factor , Disjunct, Conjunct, Literal } BS = { Z, 2, String, Rel, {+, −}, {∗, /} } F = { seq : Command × Commands → Commands, embed : Command → Commands, block : Commands → Command , assign : String × Sum → Command , cond : Disjunct × Command × Command → Command , cond1, loop : Disjunct × Command → Command , sum : Prod × Sumsect → Sum, sumsect : {+, −} × Prod × Sumsect → Sumsect, nilS : 1 → Sumsect, prod : Factor × Prodsect → Prod , prodsect : {∗, /} × Factor × Prodsect → Prodsect, nilP : 1 → Prodsect, embedI : Z → Factor , var : String → Factor , encloseS : Sum → Factor , S 93 disjunct : Conjunct × Disjunct → Disjunct, embedC : Conjunct → Disjunct, conjunct : Literal × Conjunct → Conjunct, embedL : Literal → Conjunct, not : Literal → Literal , atom : Sum × Rel × Sum → Literal , embedB : 2 → Literal , encloseD : Disjunct → Literal } Z.B. hat das JavaLight-Programm fact = 1; while x > 1 {fact = fact ∗ x; x = x − 1; } folgenden Syntaxbaum: 94 Seq Assign Embed fact Sum Loop Prod NilS EmbedC EmbedI NilP Block EmbedL 1 Seq Atom Sum > Sum Prod NilS Var NilP x Assign Embed fact Sum Assign Prod NilS EmbedI NilP 1 Prod NilS Var Prodsect x Sum Prod fact * Var NilP Var NilP x x Sumsect - Prod NilS EmbedI NilP 1 javaToAlg "prog" 1 (siehe Java.hs) u¨bersetzt das JavaLight-Programm in der Datei prog in den zugeh¨origen Syntaxbaum und schreibt diesen in die Datei Pix/javaterm.svg. Die Konvertierung nach pdf geht schnell mit https://cloudconvert.org/svg-to-pdf. 95 Beispiel 4.7 Σ(XMLstore) S = = BS = F = { { { { Store, Orders, Order, P erson, Emails, Email, Items, Item, Stock, ItemS, Suppliers, Id } String, Z } store : Stock → Store, storeO : Orders × Stock → Store, orders : P erson × Items × Orders → Orders, embedP : P erson × Items → Orders, person : String → P erson, personE : String × Emails → P erson, emails : Email × Emails → Emails, none : 1 → Emails, email : String → Email, items : Id × String × Items → Items, embedI : Id × String → Items, stock : Id × Z × Supplier × Stock → Stock, embedS : Id × Z × Supplier → Stock, supplier : P erson → Suppliers, parts : Stock → Suppliers, id : String → Id } 96 StoreO EmbedP PersonE Stock EmbedI John Mitchell Emails Id 10 Supplier Id 100 IG8 Email None I18F j.mitchell@yahoo.com EmbedS PersonE Id 30 Parts Al Jones Emails Email J38H Emails Id 10 Supplier a.j@gmail.com Email None J38H1 a.j@dot.com Stock PersonE EmbedS Id 20 Supplier Richard Bird None J38H2 PersonE Mick Taylor None Syntaxbaum des XML-Dokumentes von Beispiel 4.3 xmlToAlg "xmldoc" 1 (siehe Compiler.hs) u¨bersetzt das XMLstore-Dokument in der Datei xmldoc in den zugeho¨rigen Syntaxbaum u¨bersetzt und schreibt diesen in die Datei Pix/xmlterm.svg. xmlToAlg "xmldoc" 2 (siehe Compiler.hs) u¨bersetzt xmldoc in eine Listen-ProdukteSummen-Darstellung und schreibt diese in die Datei Pix/xmllist.svg. Fu¨r Beispiel 4.3 sieht sie folgendermaßen aus: 97 () [] [] () () John Mitchell [] j.mitchell@yahoo.com [] () () () IG8 10 One J38H 30 Two Al Jones [] [] I18F 100 a.j@gmail.com a.j@dot.com () () J38H1 10 One J38H2 20 One Richard Bird [] Mick Taylor [] One und Two sind Summenkonstruktoren, alle anderen W¨orter in Knoten bezeichnen Basiselemente (Strings oder ganze Zahlen). Wort- und Ableitungsbaumalgebra Sei G = (S, BS, Z, R) eine CFG. Neben TΣ(G) lassen sich auch die Menge der W¨orter u¨ber X = Z ∪ der Ableitungsba¨ume von G zu Σ(G)-Algebren erweitern. S BS und die Menge 98 Word (G), die Wortalgebra von G Fu¨r alle s ∈ S, Word (G)s =def X ∗. Die Tr¨agermengen der Wortalgebra h¨angen also nicht von den Regeln von G ab. Diese gehen jedoch in die Interpretation der Funktionssymbole von Σ(G) ein: Fu¨r alle r = (s → e1 . . . en) ∈ R, Word (G) fr : (X ∗)k → X ∗ (wi1 , . . . , wik ) 7→ v1 . . . vn, wobei {i1, . . . , ik } = {1 ≤ i ≤ n | ei ∈ S ∪ BS} und fu¨r alle 1 ≤ i ≤ n, ei falls ei ∈ Z, vi = wi falls ei ∈ S ∪ BS. Z.B. ist Regword (CS) (siehe 2.6) die Wortalgebra von REG (siehe Beispiel 4.1). t ∈ TΣ(G) ist ein G-Syntaxbaum fu ¨ r w ∈ X ∗, falls fold Word (G)(t) = w gilt. G ist eindeutig, wenn fold Word (G) injektiv ist. Die Sprache von G L(G) ist die Menge der W¨orter u¨ber X, die sich aus der Faltung eines Syntaxbaums in Word (G) ergeben: L(G) =def img(fold Word (G)). 99 Zwei Grammatiken G und G0 heißen ¨ aquivalent, wenn ihre Sprachen u¨bereinstimmen. Die Theorie formaler Sprachen liefert eine ¨aquivalente Definition von L(G), die die oben definierte Ableitungsrelation →G verwendet: Fu¨r alle s ∈ S, + L(G)s =def {v ∈ w | s →G w}. Beispiel (siehe Beispiele 4.1 und 4.4) L(REG) ist die Menge aller Wo¨rter u¨ber CS ∪ {, ∅, +, ·,∗ , (, )}, die regula¨re Ausdru¨cke u¨ber CS darstellen. L(REG) darf nicht mit der Sprache eines regul¨aren Ausdrucks t ∈ TReg(CS) verwechselt werden, die als Bild der Faltung von t in der Reg(CS)-Algebra Lang(∪CS) definiert wurde (siehe Kapitel 2)! o javaToAlg "prog" 2 (siehe Java.hs) u¨bersetzt das JavaLight-Programm in der Datei prog in die Interpretation des zugeho¨rigen Syntaxbaums in Word (JavaLight) und schreibt diese in die Datei javasource. Die Inhalte von prog und javasource stimmen also miteinander u¨berein! 100 Beispiel 4.8 Nochmal die Regeln von SAB: r1 = S → aB, r2 = S → bA, r3 = S → , r4 = A → aS r5 = A → bAA, r6 = B → bS, r7 = B → aBB. Alle drei Tr¨agermengen der Wortalgebra Word (SAB) sind durch {a, b}∗ gegeben. Die Konstruktoren von Σ(SAB) werden in Word (SAB) wie folgt interpretiert: Fu¨r alle v, w ∈ {a, b}∗, f1Word (w) f2Word (w) f3Word f5Word (v, w) f7Word (v, w) = = = = = f4Word (w) = aw f6Word (w) = bw bvw avw o 101 Abl(G), die Ableitungsbaumalgebra von G Die Tr¨agermengen von Abl(G) sind induktiv definiert: • Fu¨r alle r = (s → e1 . . . en) ∈ R und t ∈ Abl(G)e1×···×en , s(t) ∈ Abl(G)s. s(t) repr¨asentiert den Baum mit Wurzelmarkierung s und Unterbaumliste t. Demnach besteht Abl(G)s aus B¨aumen, deren innere Knoten mit Sorten und deren Bl¨atter mit Elementen von Basismengen markiert sind. Sei s ∈ S, r = (s → e1 . . . en) ∈ R und {i1, . . . , ik } = {1 ≤ i ≤ n | ei ∈ S ∪ BS}. Abl(G) : Abl(G)ei1 × . . . × Abl(G)eik → Abl(G)s (ti1 , . . . , tik ) 7→ s(u1, . . . , un), ti falls i ∈ {i1, . . . , ik }, wobei fu¨r alle 1 ≤ i ≤ n, ui =def ei sonst. fr 102 Commands Command Commands fact = Sum ; Command Prod Sumsect while Disjunct Factor Prodsect Command Conjunct 1 Commands Literal Sum > Sum Prod Sumsect Factor Prodsect x Command fact = Sum ; Prod Sumsect Factor Prodsect 1 Commands Command Prod Sumsect Factor fact x = Sum ; Prodsect Prod * Factor Prodsect Factor Prodsect x x Sumsect - Prod Sumsec Factor Prodsect 1 Ableitungsbaum von fact = 1; while x > 1 {fact = fact*x; x = x-1;} javaToAlg "prog" 3 (siehe Java.hs) u¨bersetzt das JavaLight-Programm in der Datei prog in den zugeh¨origen Ableitungsbaum und schreibt diesen in die Datei Pix/javaderi.svg. 103 Beispiel 4.9 Modelle der Ausdru ¨ cke von JavaLight Wir interpretieren hier zun¨achst nur die Sorten und Operationen von Σ(JavaLight), die mit Ausdru¨cken zu tun haben Der erste Modell A ignoriert Programmvariablen (Strings), m.a.W.: der Konstruktor var : String → Factor bleibt uninterpretiert, darf also in mit A auszuwertenden Ausdru¨cken nicht vorkommen. ASum = AProd = AFactor = Z ASumsect = AProdsect = (Z → Z) ADisjunct = AConjunct = ALiteral = 2 embedDA : AConjunct embedLA : ALiteral encloseS A : ASum encloseDA : ADisjunct embedI A : Z embedB A : 2 x → → → → → → 7→ ADisjunct AConjunct AFactor ALiteral AFactor ALiteral x 104 sumA : AProd × ASumsect → ASum prodA : AFactor × AProdsect → AProd (x, f ) 7→ f (x) sumsectA : {+, −} × AProd × ASumsect → ASumsect prodsectA : {∗, /} × AFactor × AProdsect → AProdsect (op, x, f ) 7→ λy.f (y op x) nilS A : 1 → ASumsect nilP A : 1 → AProdsect 7→ λx.x disjunctA : AConjunct × ADisjunct → ADisjunct (x, y) 7→ x ∨ y conjunctA : ALiteral × AConjunct → AConjunct (x, y) 7→ x ∧ y notA : ALiteral → ALiteral x 7→ ¬x 105 atomA : ASum × Rel × ASum → ALiteral (x, rel, y) 7→ x rel y Im zweiten Modell B wird jede Tr¨agermenge M ∈ {Z, Z → Z, 2} von A zur Funktionsmenge Store → M geliftet, wobei Store = (String → Z) die Menge der Belegungen von Programmvariablen durch ganze Zahlen ist (siehe Kapitel 2): BSum = BProd = BFactor = Store → Z BSumsect = BProdsect = Store → (Z → Z) BDisjunct = BConjunct = BLiteral = Store → 2 B interpretiert embedD, embedL, encloseS und encloseD wie A als Identit¨aten. varB : String → BFactor x 7→ λstore.store(x) Die restlichen Operationen von Σ(JavaLight) werden zustandsabh¨angig gemacht: 106 embedI B : Z → BFactor embedB B : 2 → BLiteral a 7→ λstore.a sumB : BProd × BSumsect → BSum prodB : BFactor × BProdsect → BProd (f, g) 7→ λstore.g(store)(f (store)) sumsectB : {+, −} × BProd × BSumsect → BSumsect prodsectB : {∗, /} × BFactor × AProdsect → BProdsect (op, f, g) 7→ λstore.λx.g(store)(x op f (store)) nilS B : 1 → BSumsect nilP B : 1 → BProdsect 7→ λstore.λx.x 107 disjunctB : BConjunct × BDisjunct → BDisjunct (f, g) 7→ λstore.(f (store) ∨ g(store)) conjunctB : BLiteral × BConjunct → BConjunct (f, g) 7→ λstore.(f (store) ∧ g(store)) notB : BLiteral → BLiteral f 7→ λstore.¬f (store) atomB : BSum × Rel × BSum → BLiteral (f, rel, g) 7→ λstore.(f (store) rel g(store)) In Abschnitt 11.8 wird B als Teil der JavaLight-Algebra javaState in Haskell implementiert. 108 5 Interpreter, Parser und Compiler Aufbauend auf den oben behandelten algebraischen Konzepten definieren wir Interpreter, Parser und Compiler fu¨r kontextfreie Grammatiken. S Sei G = (S, BS, Z, R) eine CFG, X = Z ∪ BS und A eine Σ(G)-Algebra, in die W¨orter u¨ber X u¨bersetzt werden sollen. Die Faltung fold A von Σ(G)-Termen in A nennen wir Interpreter fu ¨ r G in A bzgl. encode und execute, falls das folgende Funktionsdiagramm kommutiert: TΣ(G) fold Sem g Sem fold A (1) encode A execute g Mach Hier sind • Sem die ebenfalls als Σ(G)-Algebra gegebene Semantik der Quellsprache L(G), • Mach ein in der Regel unabh¨angig von Σ(G) definiertes Modell der Zielsprache, meist in Form einer abstrakten Maschine, 109 • execute ein Interpreter, der Zielprogramme in der abstrakten Maschine Mach ausfu¨hrt, • encode eine Funktion, die Sem auf Mach abbildet und die gewu¨nschte Arbeitsweise des Compilers auf semantischer Ebene reflektiert. Die Initialita¨t der Termalgebra TΣ(G) erlaubt es uns, den Beweis der Kommutativita¨t von (1) auf den Nachweis der folgenden Bedingungen zu reduzieren: • Mach ist – wie Sem und A – eine Σ(G)-Algebra. Das ist in der Regel einfach, weil dazu nur die Schemata, nach denen A Zielsprachenoperationen zu Operationen von A zusammensetzt, mittels auf Mach so u¨bertragen werden mu¨ssen, dass execute Σ(G)homomorph wird, d.h. fu¨r alle Funktionssymbole f : e → s von Σ(G) muss gelten: executes ◦ f A = f Mach ◦ executee. • encode ist Σ(G)-homomorph. Damit sind alle vier Abbildungen in Diagramm (1), also auch und die beiden Kompositionen execute◦fold A und encode◦fold Mach Σ(G)-Homomorphismen. Da TΣ(G) die initiale Σ(G)Algebra ist, sind beide Kompositionen gleich. Also kommutiert (1). Wa¨hrend fold A Syntaxba¨ume in der Zielsprache auswertet, ordnet fold Word (G) (siehe Kapitel 4) einem Syntaxbaum t das Wort der Quellsprache zu, aus dem ein Parser fu¨r G t berechnen soll. 110 In der Theorie formaler Sprachen ist der Begriff Parser auf Entscheidungsalgorithmen beschr¨ankt, die anstelle von Syntaxb¨aumen lediglich einen Booleschen Wert liefern, der angibt, ob ein Eingabewort zur Sprache der jeweiligen Grammatik geho¨rt oder nicht. Demgegenu¨ber definieren wir einen Parser fu ¨ r G als eine S-sortige Funktion parseG : X ∗ → M (TΣ(G)), die entweder Syntaxb¨aume oder, falls das Eingabewort nicht zur Sprache von G geh¨ort, Fehlermeldungen erzeugt. Welche Syntaxba¨ume bzw. Fehlermeldungen ausgegeben werden sollen, wird durch die Monade M festgelegt, das ist ein Funktor zusammen mit zwei natu ¨ rlichen Transformationen η : Id K → M (Einheit) und µ : M M → M (Multiplikation). 5.1 Funktoren und Monaden Funktoren, natu¨rlichen Transformationen und Monaden sind kategorientheoretische Grundbegriffe, die heutzutage jeder Softwaredesigner kennen sollte, da sie sich in den Konstruktionsund Transformationsmustern jedes denkbaren statischen, dynamischen oder hybriden Systemmodells wiederfinden. Die hier ben¨otigten kategorientheoretischen Definitionen lauten wie folgt: 111 Eine Kategorie K besteht aus • einer – ebenfalls mit K bezeichneten – Klasse von K-Objekten, • fu¨r alle A, B ∈ K einer Menge K(A, B) von K-Morphismen, • einer assoziativen Komposition ◦ : K(A, B) × K(B, C) → K(A, C) (f, g) 7−→ g ◦ f, • einer Identit¨ at idA ∈ K(A, A), die bzgl. ◦ neutral ist, d.h. fu¨r alle B ∈ K und f ∈ K(A, B) gilt f ◦ idA = f = idB ◦ f . Im Kontext einer festen Kategorie K schreibt man meist f : A → B anstelle von f ∈ K(A, B). Wir ben¨otigen hier drei Kategorien: die Kategorien Set und SetS , deren Objekte alle (Ssortigen) Mengen und deren Morphismen alle (S-sortigen) Funktionen sind sowie die Unterkategorie AlgΣ von SetS , deren Objekte alle Σ-Algebren und deren Morphismen alle Σ-Homomorphismen sind. Seien K, L Kategorien. Ein Funktor F : K → L ist eine Funktion, die jedem K-Objekt ein L-Objekt und jedem K-Morphismus f : A → B einen L-Morphismus F (f ) : F (A) → F (B) zuordnet sowie folgende Gleichungen erfu¨llt: 112 • Fu¨r alle K-Objekte A, F (idA) = idF (A), (2) • Fu¨r alle K-Morphismen f : A → B and g : B → C, F (g ◦ f ) = F (g) ◦ F (f ). (3) Zwei Funktoren F : K → L und G : L → M kann man wie andere Funktion sequentiell komponieren: Fu¨r alle K-Objekte und -Morphismen A, (GF )(A) =def G(F (A)). Beispiele Sei B ∈ L. Der konstante Funktor const(B) : K → L ordnet jedem K-Objekt das L-Objekt B zu und jedem K-Morphismus die Identit¨at auf B. Der Identit¨ atsfunktor Id K : K → K ordnet jedem K-Objekt und jedem K-Morphismus sich selbst zu. Der Listenfunktor Lists : Set → Set ordnet jeder Menge A die Menge A∗ der W¨orter u¨ber A zu und jeder Funktion f : A → B die Funktion Lists(f ) = map(f ) : A∗ → B ∗ (a1, . . . , an) 7→ (f (a1), . . . , f (an)) Der Mengenfunktor Sets : Set → Set ordnet jeder Menge A die Potenzmenge P(A) zu und jeder Funktion f : A → B die Funktion Sets(f ) : P(A) → P(B) C 7→ f (C) = {f (c) | c ∈ C} 113 Sei E eine Menge von Fehlermeldungen, Ausnahmewerten (exceptions) o.¨a. Bei der Implementierung monadischer Compiler in Kapitel 16 werden wir den folgenden Exceptionfunktor E + : Set → Set verwenden, der jeder Menge A die Menge E + A zuordnet und jeder Funktion f : A → B die Funktion E+f :E+A → E+B (e, 1) 7→ (e, 1) (a, 2) 7→ (f (a), 2) Sei C eine Menge. Der Potenzfunktor C : Set → Set ordnet jeder Menge A die Menge C → A zu und jeder Funktion f : A → B die Funktion f C : AC → B C g 7→ f ◦ g Sei S eine Zustandsmenge. Der Transitionsfunktor ( × S)S : Set → Set ordnet jeder Menge A die Menge S → A × S zu und jeder Funktion f : A → B die Funktion (f × S)S : (A × S)S → (B × S)S g 7→ λs.(f (π1(g(s))), π2(g(s))) Aufgabe Zeigen Sie, dass die hier definierten Funktionen tats¨achlich Funktoren sind, also (2) und (3) erfu¨llen. o 114 Seien F, G : K → L Funktoren. Eine natu ¨ rliche Transformation τ : F → G ordnet jedem K-Objekt A einen L-Morphismus τA : F (A) → G(A) derart, dass fu¨r alle KMorphismen f : A → B folgendes Diagramm kommutiert: τA F (A) G(A) F (f ) g F (B) G(f ) τB g G(B) Ein Funktor M : K → K heißt Monade, wenn es zwei natu¨rliche Transformationen η : Id K → M (Einheit) und µ : M ◦ M → M (Multiplikation) gibt, die fu¨r alle A ∈ K das folgende Diagramm kommutativ machen: M (A) ηM (A) M (ηA) M (M (A)) ≺ M (A) (4) A M (id ) µA g ≺ M (A) (5) A M (id ) M (M (M (A))) M (µA) g M (M (A)) µM (A) M (M (A)) (6) µA µA g M (A) 115 Beispiele Alle o.g. Funktoren sind Monaden. Einheit bzw. Multiplikation sind wie folgt definiert: Seien A und S Mengen. • Listenmonade: ηA : A → A∗ a 7→ a µA : (A∗)∗ → A∗ (w1, . . . , wn) 7→ w1 . . . wn • Mengenmonade: ηA : A → P(A) a 7→ {a} µA : P(P(A)) → P(A) S S 7→ S • Exceptionmonade: ηA : A → E + A a 7→ (a, 2) µA : E + (E + A) (e, 1) ((e, 1), 2) ((a, 2), 2) → 7 → 7 → 7 → E+A (e, 1) (e, 1) (a, 2) • Potenzmonade: ηA : A → AS a 7→ λs.a • Transitionsmonade: ηA : A → (A × S)S a 7→ λs.(a, s) µA : (AS )S → AS f 7→ λs.f (s)(s) µA : ((A × S)S × S)S → (A × S)S f 7→ λs.π1(f (s))(π2(f (s))) 116 Aufgabe Zeigen Sie, dass diese Einheiten bzw. Multiplikationen tatsa¨chlich natu¨rliche Transformationen sind, also (4)-(6) erfu¨llen. o Die bind-Operatoren = : (M (A) × M (B) → M (B), : (M (A) × (A → M (B)) → M (B) werden wie folgt aus der Funktoreigenschaft und der Multiplikation von M abgeleitet: Fu¨r alle A, B ∈ K, m ∈ M (A), f : A → M (B) und m0 ∈ M (B), m m0 = m = λa.m0, m = f = µB (M (f )(m)). (7) Intuitiv stellt man sich ein monadisches Objekt m ∈ M (A) als Berechnung vor, die eine – evtl. leere – Menge von Werten in A erzeugt. Ein Ausdruck der Form m = f wird dann wie folgt ausgewertet: Die von m berechneten Werte a ∈ A werden als Eingabe an die Berechnung f u¨bergeben und von f (a) verarbeitet. Die Betrachtung der Elemente von M (A) als Berechnungen, die eine Ausgabe in A produzieren, l¨asst sich besonders gut am Beispiel der Transitionsmonade begru¨nden. Aus der Multiplikation der Transitionsmonade und der allgemeinen Definition des bindOperators ergibt sich n¨amlich die folgende Charakterisierung des bind-Operators der Transitionsmonade: 117 Fu¨r alle g : S → A × S und f : A → (S → B × S), g = f = µB (M (f )(g)) = µB ((f × S)S (g)) = µB (λs.(f (π1(g(s))), π2(g(s)))) = λs.π1((λs.(f (π1(g(s))), π2(g(s))))(s))(π2((λs.(f (π1(g(s))), π2(g(s))))(s))) = λs.π1(f (π1(g(s))), π2(g(s)))(π2(f (π1(g(s))), π2(g(s)))) = λs.f (π1(g(s)))(π2(g(s))). Die Anwendung der Transformation (g = f ) : S → B × S auf den Zustand s besteht demnach • in der Anwendung der Transformation g auf s, die die Ausgabe a = π1(g(s)) und den Folgezustand s0 = π2(g(s)) liefert, • und der darauffolgenden Anwendung der Transformation f (a) auf s0. Sei a ∈ A, m ∈ M (A), f : A → M (B) und g : B → M (C). Aus (4)-(7) erh¨alt man die folgenden Eigenschaften von =: m = ηA = m, ηA(a) = f = f (a), (m = f ) = g = m = λa.f (a) = g. (8) (9) (10) 118 Umgekehrt lassen sich die Funktoreigenschaft und die Multiplikation von M sowie (4)-(7) aus den bind-Operatoren und (8)-(10) herleiten, sofern fu¨r alle h : A → B, m ∈ M (A) und m0 ∈ M (M (A)) wie folgt definiert werden: M (h)(m) = m = ηB ◦ h, µA(m0) = m0 = idM (A). (11) (12) Neben der durch = gegebenen sequentiellen Komposition monadischer Berechnungen verwenden monadische Compiler zur Realisierung von Backtracking eine als natu¨rliche Transformation definierte parallele Komposition ⊕ : M × M → M , um mehrere Teilcompiler auf dieselbe Eingabe anzuwenden und deren Ergebnisse zu verknu¨pfen. Wir bezeichnen M als Plusmonade, wenn ⊕ assoziativ und mit M vertr¨aglich ist und es eine bzgl. ⊕ linksneutrale und bzgl. = links annihilierende natu¨rliche Transformation zero : Id K → M gibt, d.h. fu¨r alle m, m0, m00 ∈ M (A), h : A → B und f : A → M (B) gilt, (m ⊕ m0) ⊕ m00 M (h)(m ⊕ m0) zeroA ⊕ m zeroA = f = = = = m ⊕ (m0 ⊕ m00), M (h)(m) ⊕ M (h)(m0), m, zeroB . (12) (13) (14) (15) 119 Beispiele Einige der o.g. Monaden sind Plusmonaden: Seien A, B, S Mengen. • Listenmonade: ⊕ : A∗ × A∗ → A∗ (v, w) 7→ vw zero = [] • Mengenmonade: ⊕ : P(A) × P(A) → P(A) (s, s0) 7→ s ∪ s0 zero = ∅ • Exceptionmonade: E enthalte ein ausgezeichnetes Element err. ⊕ : (E + A) × (E + A) → E + A ((e, 1), m) 7→ m ((a, 2), m) 7→ (a, 2) zero = (err, 1) Lemma 5.2 Die Exceptionmonade erfu¨llt (12)-(15). Beweis. Fu¨r alle e, e0 ∈ E, a ∈ A, m, m0 ∈ E + A, h : A → B und f : A → E + B, ((e, 1) ⊕ (e0, 1)) ⊕ m = (e0, 1) ⊕ m = m = (e0, 1) ⊕ m = (e, 1) ⊕ ((e0, 1) ⊕ m), ((e, 1) ⊕ (a, 2)) ⊕ m = (a, 2) ⊕ m = (a, 2) = (e, 1) ⊕ (a, 2) = (e, 1) ⊕ ((a, 2) ⊕ m), ((a, 2) ⊕ m) ⊕ m0 = (a, 2) ⊕ m0 = (a, 2) = (a, 2) ⊕ (m ⊕ m0), 120 M (h)((e, 1) ⊕ m) = M (h)(m) = (e, 1) ⊕ M (h)(m) = M (h)(e, 1) ⊕ M (h)(m), M (h)((a, 2) ⊕ m) = M (h)(a, 2) = (h(a), 2) = (h(a), 2) ⊕ M (h)(m) = M (h)(a, 2) ⊕ M (h)(m), zero ⊕ m = (err, 1) ⊕ m = m, zero = f = µB (M (f )(zero)) = µB (M (f )(err, 1)) = µB (err, 1) = (err, 1) = zero. o Potenz- und Transitionsmonaden k¨onnen Plusmonaden huckepack nehmen und werden damit zu weiteren Plusmonaden: • Huckepack-Potenzfunktor: Sei M eine Plusmonade. M 0 =def M ( )S : Set → Set Fu¨r alle h : A → B, M 0(h) : M 0(A) f ηA0 : A a ⊕0 : M 0(A) × M 0(A) (f, g) → 7 → → 7→ → 7→ M 0(B) M (h) ◦ f M 0(A) µ0A : M 0(M 0(A)) → M 0(A) λs.η(a) f 7→ λs.f (s)(s) M 0(A) λs.f (s) ⊕ g(s) 121 • Huckepack-Transitionsfunktor: Sei M eine Plusmonade. M 0 =def M ( × S)S : Set → Set Fu¨r alle h : A → B, M 0(h) : M 0(A) f ηA0 : A a µ0A : M 0(M 0(A)) f ⊕0 : M 0(A) × M 0(A) (f, g) → 7 → → 7 → → 7 → → 7→ M 0(B) λs.f (s) = λ(a, s0).η(h(a), s0) M 0(A) λs.η(a, s) M 0(A) λs.f (s) = λ(g, s0).g(s0) M 0(A) λs.f (s) ⊕ g(s) Die Implementierung von Monaden in Haskell wird in Kapitel 15 behandelt, monadische Compiler in Kapitel 16. Letztere sind Huckepack-Transitionsmonaden, wobei die m¨oglichen Eingaben die Zustandsmenge S bilden. Weitere Monadenbeispiele finden sich in [25], Kapitel 9. 122 Zuru¨ck zur Definition eines Parser als Funktion des Typs parseG : X ∗ → M (TΣ(G)). Hier ist M keine Transitionsmonade, sondern eine Exception-, Listen- oder Mengenmonade. Im ersten Fall w¨are parseG partiell, aber deterministisch, im zweiten Fall nichtdeterministisch. Folglich ist eine Exceptionmonade immer dann geeignet, wenn G eindeutig ist, w¨ahrend die Listen- oder Mengenmonade nur fu¨r mehrdeutige CFGs infrage kommt, aber auch dann nicht zwingend ist: Auch wenn ein Eingabewort mehrere Syntaxb¨aume hat, braucht der Parser nur einen davon erzeugen. Einen Ausnahmefall gibt es auch im Fall der Listen- oder Mengenmonade, n¨amlich die leere Liste bzw. Menge. Zur Definition der Korrektheit von parseG verwenden wir eine – an [5, 20]) angelehnte – monadenbasierte dynamische Logik (MDL): Seien M eine Monade, A eine Menge, m ∈ M (A) und ϕ : A → 2. Die MDL-Formel [m]ϕ steht fu¨r die Gleichung M (ϕ)(m) = M (λa.1)(m). parseG ist korrekt, wenn alle w ∈ X ∗ die folgende MDL-Formel erfu¨llen: [parseG(w)](λt.fold Word (G)(t) = w). (16) 123 Wie bereits in Kapitel 1 informell beschrieben wurde, komponiert ein generischer Compiler fu ¨ r G, compileG, einen Parser fu¨r G mit der Faltung der vom Parser erzeugten Syntaxba¨ume in einer beliebigen Zielsprache, die als Σ(G)-Algebra A formuliert ist: compileA G M (fold A ) ∗ parseG = X −→ M (TΣ(G)) −→ M (A). (17) Wegen der Initialit¨at von TΣ(G) stimmt fold TΣ(G) mit der Identit¨at auf TΣ(G) u¨berein. Folglich ist parseG die TΣ(G)-Instanz von compileG: parseG = idM (TΣ(G)) ◦ parseG = M (idTΣ(G) ) ◦ parseG = M (fold TΣ(G) ) ◦ parseG (17) = T compileGΣ(G) . (18) Lemma 5.3 Sei (17) erfu¨llt. Dann sind (16) und Word (G) [compileG (w)](λv.v = w) (19) ¨aquivalent. Beweis. Sei T = TΣ(G) und W = Word (G). Wegen (18) ist (16) ¨aquivalent zu [compileTG(w)](λt.fold W (t) = w). (20) 124 Es gelte (20). Dann erhalten wir (19) wie folgt: Fu¨r alle w ∈ X ∗, M (λv.v = w)(compileW G (w)) M ist Funktor = (17),(18) = M (λv.v = w)(M (fold W )(compileTG(w))) M ((λv.v = w) ◦ fold W )(compileTG(w)) (11) = compileTG(w) = η2 ◦ (λv.v = w) ◦ fold W = compileTG(w) = λt.η2(fold W (t) = w) = compileTG(w) = η2 ◦ λt.fold W (t) = w (11) = M (λt.fold W (t) = w)(compileTG(w)) (11) (20) = M (λt.1)(compileTG(w)) = compileTG(w) = η2 ◦ λt.1 = compileTG(w) = λt.η2(1) = compileTG(w) = η2 ◦ (λv.1) ◦ fold W (11) = M ((λv.1) ◦ fold W )(compileTG(w)) M ist Funktor = M (λv.1)(M (fold W )(compileTG(w))) (17),(18) = M (λv.1)(compileW G (w)). Durch Umordnung dieser Gleichungen erh¨alt man (20) aus (19). o Aus Lemma 5.3 ergibt sich (19) neben (17) als Anforderung an compileG. 125 In Analogie zu den Abschnitt 2.9 definierten Erkennern regula¨rer Sprachen als charakteristische Funktionen ist compile1G : X ∗ → M (1) = 1 + 1 ∼ =2 ein Erkenner der kontextfreien Sprache L(G). M ist hier die Exceptionmonade mit E = 1. Die zweite 1 steht fu¨r die finale Σ(G)-Algebra mit der Tr¨agermenge 1 (siehe Kapitel 2). (1) und (17) liefern die Korrektheit von compileA G bzgl. encode und execute im Sinne der Kommutativita¨t des folgenden – dem Interpreterdiagramm (1) entsprechenden – Compilerdiagramms: X ∗ compileA G M (A) compileSem G M (execute) g M (Sem) g M (Mach) M (encode) (17) A M (execute) ◦ compileA G = M (execute) ◦ M (fold ) ◦ parseG (1) M ist Funktor M (execute ◦ fold A) ◦ parseG = M (encode ◦ fold Sem) ◦ parseG M ist Funktor M (encode) ◦ M (fold Sem) ◦ parseG = M (encode) ◦ compileSem G . = = (17) 126 Ist M eine Plusmonade, dann liefert die Funktion sat : M (A) → ((A → 2) → M (A)) m 7→ λϕ.m = λa.if ϕ(a) then η(a) else zero das folgende Kriterium fu¨r die Gu¨ltigkeit einer MDL-Formel [m]ϕ: Lemma 5.4 Fu¨r alle ϕ : A → 2 und m ∈ M (A), sat(m)(ϕ) = m ⇒ [m]ϕ. (21) Beweis. Sei sat(m)(ϕ) = m. (11) M (ϕ)(m) = M (ϕ)(sat(m)(ϕ)) = sat(m)(ϕ) = η ◦ ϕ = (m = λa.if ϕ(a) then η(a) else zero) = η ◦ ϕ (10) = m = λa.(if ϕ(a) then η(a) else zero) = η ◦ ϕ = m = λa.if ϕ(a) then (η(a) = η ◦ ϕ) else (zero = η ◦ ϕ) (9),(15) = m = λa.if ϕ(a) then η(ϕ(a)) else zero = m = λa.if ϕ(a) then η(1) else zero. 127 (11) M (λx.1)(m) = M (λa.1)(sat(m)(ϕ)) = sat(m)(ϕ) = η ◦ λa.1 = (m = λa.if ϕ(a) then η(a) else zero) = η ◦ λa.1 (10) = m = λa.(if ϕ(a) then η(a) else zero) = η ◦ λa.1 = m = λa.if ϕ(a) then (η(a) = η ◦ λa.1) else (zero = η ◦ λa.1) (9),(15) = m = λa.if ϕ(a) then η(1) else zero. Symmetrie und Transitivit¨at von = liefern M (ϕ)(m) = M (λx.1)(m). o 128 6 LL-Compiler Sei G = (S, BS, Z, R) eine nicht-linksrekursive CFG, SBZ = S ∪ BS ∪ {{z} | z ∈ Z}, S X = Z ∪ BS (Menge der Eingabesymbole), M eine Plusmonade und A eine Um M zur Ausgabe nicht nur korrekter Ergebnisse, sondern auch von Fehlermeldungen einzusetzen, setzen wir voraus, dass es eine natu¨rliche Transformation ∗ errmsg : Id Set → M X =def ( X∗ )◦M gibt, die fu¨r alle Funktionen f : A → B folgende Gleichung erfu¨llt: M (f ) ◦ errmsgA = errmsgB , (1) so dass Fehlermeldungen durch jede Folgeberechnung propagiert werden. Sei A eine Σ(G)-Algebra. Der in diesem Kapitel definierte – SBZ-sortierte – Compiler ∗ compileA G : X → M (A) = ∗ (compileA G,s : X → M (As ))s∈SBZ u¨bertr¨agt die Arbeitsweise eines klassischen LL-Parsers auf Compiler mit Backtracking. Das erste L steht fu¨r seine Verarbeitung des Eingabewortes von links nach rechts, das zweite L fu¨r seine – implizite – Konstruktion einer Linksableitung. Da diese Arbeitsweise dem mit der Wurzel beginnenden schrittweisen Aufbau eines Syntaxbaums entspricht, werden LL-Parser auch top-down-Parser genannt. 129 Monadische Ausdru¨cke werden in Haskell oft mit der do-Notation wiedergegeben. Sie verdeutlicht die Korrespondenz zwischen monadischen Berechnungen einerseits und imperativen Programmen andererseits. Die Ru¨cku¨bersetzung der do-Notation in die urspru¨nglichen monadischen Ausdru¨cke ist induktiv wie folgt definiert: Sei a ∈ A. do m; x ← m0 = m = λx. do m0 do m; m0 = m do m0 Gleichung (10) von Kapitel 5 impliziert die Assoziativita¨t des Sequentialisierungsoperators (;), d.h. do m; m0; m00 ist gleichbedeutend mit do (do m; m0); m00 und do m; (do m0; m00). Fu¨r alle w ∈ X ∗, A compileA G (w) =def do (a, w) ← trans (w); if w = then η(a) else errmsg(w) (2) Die SBZ-sortierte Transitionsfunktion transA : X ∗ → M (A × X ∗) = ∗ ∗ (transA s : X → M (As × X ))s∈SBZ erkennt das l¨angste u¨bersetzbare Pr¨afix eines Eingabewortes w, u¨bersetzt es in ein Element von A und gibt dieses zusammen mit der verbleibenden Resteingabe (Suffix von w) zuru¨ck. 130 transA ist wie folgt definiert: Fall 1: s ∈ BS ∪ {{z} | z ∈ Z}. Fu¨r alle x ∈ X und w ∈ X ∗, transA s (xw) = if x ∈ s then η(x, w) else errmsg(xw) transA = errmsg() s () Fall 2: s ∈ S. Seien ⊕ : M (As) × M (As) → M (As) die parallele Komposition von M (As) und r1, . . . , rm die Regeln von R mit linker Seite s. Fu¨r alle w ∈ X ∗, A A transA (4) s (w) = tryr1 (w) ⊕ · · · ⊕ tryrm (w). Sei 1 ≤ i ≤ m, ri = (s → e1 . . . en) und {j1, . . . , jk } = {1 ≤ j ≤ n | ej ∈ S ∪ BS}. Fu¨r alle w ∈ X ∗, A do (a , w) ← trans 1 e1 (w); .. . tryrAi (w) = (an, w) ← transA en (w); η(f A(a , . . . , a ), w) jk ri j1 131 Die Summanden von (4) mu¨ssen so angeordnet werden, dass stets l¨angere vor ku¨rzeren zu einem korrekten Ergebnis fu¨hrende Pr¨afixe des jeweiligen Eingabewortes verarbeitet werden. W¨ahrend alle Summanden von (4) auf das gesamte Eingabewort w angewendet werden, wird es von tryrAi Symbol fu¨r Symbol verarbeitet: Zun¨achst wird transA e1 auf w angewendet, A A dann transA e2 auf das von transe1 nicht verarbeitete Suffix w1 von w, transe3 auf das von transA e2 nicht verarbeitete Suffix w2 von w1 , usw. Gibt es 1 ≤ j ≤ n derart, dass transA ej (w) scheitert, d.h. existiert kein aus ej ableitbares Pr¨afix von w, dann u¨bergibt tryrAi das gesamte Eingabewort zur Parsierung an den n¨achsten Teilcompiler tryrAi+1 (Backtracking). Ist transA ¨r alle 1 ≤ j ≤ n erfolgreich, dann schließt der Aufruf von tryrAi mit ej (w) jedoch fu der Anwendung von frAi auf die Zwischenergebnisse aj1 , . . . , ajk ∈ A. Klassische LL-Parser sind deterministisch, d.h. sie erlauben kein Backtracking, sondern setzen voraus, dass die ersten k Symbole des Eingabewortes w bestimmen, welcher der A Teilcompiler try1A, . . . , trym aufgerufen werden muss, um zu erkennen, dass w zu L(G)s geh¨ort. CFGs, die diese Voraussetzung erfu¨llen, heißen LL(k)-Grammatiken. Im Gegensatz zur Nicht-Linksrekursivit¨at l¨asst sich die LL(k)-Eigenschaft nicht immer durch eine Transformation der Grammatik erzwingen, auch dann nicht, wenn sie eine von einem deterministischen Kellerautomaten erkennbare Sprache erzeugt! 132 Beispiel 6.1 SAB (siehe Beispiel 4.5) Die Regeln r1 = S → aB, r2 = S → bA, r3 = S → , r4 = A → aS, r5 = A → bAA, r6 = B → bS, r7 = B → aBB von SAB liefern nach obigem Schema die folgende Transitionsfunktion des LL-Compilers in eine beliebige SAB-Algebra alg: Fu¨r alle x, z ∈ {a, b} und w ∈ {a, b}∗, trans_z(xw) trans_z() trans_S(w) trans_A(w) trans_B(w) try_r1(w) try_r2(w) try_r3(w) = = = = = if x = z then η(z,w) else errmsg(xw) errmsg() try_r1(w) ⊕ try_r2(w) ⊕ try_r3(w) try_r4(w) ⊕ try_r5(w) try_r6(w) ⊕ try_r7(w) = do (x,w) <- trans_a(w); (c,w) <- trans_B(w); = do (x,w) <- trans_b(w); (c,w) <- trans_A(w); = η(f_r3^alg,w) η(f_r1^alg(c),w) η(f_r2^alg(c),w) 133 try_r4(w) try_r5(w) try_r6(w) try_r7(w) = do (x,w) (c,w) = do (x,w) (c,w) (d,w) <<<<<- trans_a(w); trans_S(w); trans_b(w); trans_A(w); trans_A(w); = do (x,w) (c,w) = do (x,w) (c,w) (d,w) <<<<<- trans_b(w); trans_S(w); trans_a(w); trans_B(w); trans_B(w); η(f_r4^alg(c),w) η(f_r5^alg(c,d),w) η(f_r6^alg(c),w) η(f_r7^alg(c,d),w) o transs(w) kann scheitern, obwohl w zur Sprache von G geh¨ort, weil die Regeln von G nicht so angeordnet werden ko¨nnen, dass in (2) fu¨r alle 1 ≤ i < j ≤ m kein echtes Pra¨fix eines aus der rechten Seite von rj ableitbaren Wortes aus der rechten Seite von ri ableitbar ist. Man muss dann schauen, in welchen Kontexten diese Wo¨rter in L(G) auftreten ko¨nnen und die rechten Seiten von ri bzw. rj um diese Kontexte erweitern. 134 Beispiel 6.2 Ein solcher Fall wu¨rde z.B. in JavaLight+ auftreten (siehe Kapitel 13), wenn wir dort anstelle der drei Sorten ExpSemi , ExpBrac und ExpComm die zun¨achst naheliegende eine Sorte Exp und die folgenden Regeln verwenden wu¨rden: Command Exp Sum Prod Factor Disjunct Conjunct Literal Actuals Actuals0 → → → → → → → → → → String = Exp; | write Exp; | . . . Sum | Disjunct Prod | . . . Factor | . . . String | String Actuals | . . . Conjunct | . . . Literal | . . . String | String Actuals | . . . () | (Actuals0 Exp) | Exp, Actuals0 (A) (B) (C) Ein aus diesen Regeln gebildeter LL-Compiler fu¨r Command wu¨rde z.B. die Eingaben z = x<=11; und write x<=11; als syntaktisch inkorrekt betrachten, weil der von ihm aufgerufene Compiler fu¨r Exp zun¨achst nach einem arithmetischen Ausdruck sucht, x als solchen erkennt und deshalb den Booleschen Ausdruck x ≤ 11 nicht bis zum Ende liest. 135 Die Ersetzung von Regel (B) durch Exp → Disjunct | Sum l¨ost das Problem nicht, denn nun sucht der Compiler fu¨r Exp nach einem Booleschen Ausdruck, erkennt x als solchen, liest also auch nicht bis zum Ende von x ≤ 11. Ersetzt man hingegen (A)-(C) durch Command ExpSemi ExpBrac ExpComm Actuals0 → → → → → String = ExpSemi | write ExpSemi | . . . Sum; | Disjunct; Sum) | Disjunct) Sum, | Disjunct, ExpBrac | ExpComm Actuals0 dann wird alles gut: Jetzt erzwingt das auf Sum oder Disjunct folgende Terminal (Semikolon, schließende Klammer bzw. Komma), dass die Compiler fu¨r ExpSemi , ExpBrac und ExpComm die jeweilige Resteingabe stets bis zu diesem Terminal verarbeiten. Natu¨rlich genu¨gt ein Compiler fu¨r alle drei Sorten. Man muss ihn lediglich mit dem jeweiligen Terminal parametrisieren (siehe Java2.hs). o 136 Satz 6.4 transA ist wohldefiniert. Beweis durch Noethersche Induktion. Da S endlich und G nicht linksrekursiv ist, ist die folgende Ordnung > auf SBZ Noethersch, d.h., es gibt keine unendlichen Ketten s1 > s2 > s3 > . . . s > s0 ⇔def + ∃ w ∈ SBZ ∗ : s →G s0w. Wir erweitern > zu einer ebenfalls Noetherschen Ordnung auf X ∗ × SBZ: (v, s) (w, s0) ⇔def |v| > |w| oder (|v| ≥ |w| und s > s0). Nach Definition von transs(w) gibt es r = (s → e1 . . . en) ∈ R mit A A A transA s (w) = . . . transe1 (w) . . . transe2 (w1 ) . . . transen (wn−1 ) . . . Da w1, . . . , wn echte Suffixe von w sind, gilt s > e1 und |w| > |wi| fu¨r alle 1 ≤ i < n, also (w, s) (w, e1) und (w, s) (wi−1, ei) fu¨r alle 1 < i ≤ n. Die rekursiven Aufrufe von transA haben also bzgl. kleinere Argumente. Folglich terminiert jeder Aufruf von transA, m.a.W.: transA ist wohldefiniert. o Der Nachweis von Gleichung (17) in Kapitel 5 fu¨r unseren LL-Compiler verwendet die folgenden Zusammenh¨ange zwischen einer Monade und ihrem bind-Operator: 137 Lemma 6.5 Sei M eine Monade, h : A → B, m ∈ M (A) und f : A → M (A). M (h)(m = f ) = m = M (h) ◦ f, M (h)(m) = f = m = f ◦ h. (5) (6) Sei M eine Monade, h : A → B, n > 0 und f : An → A. Fu¨r alle m1, . . . , mn ∈ M (A), M (h)(m1 = λa1.m2 = · · · = λan.ηA(f (a1, . . . , an))) = m1 = λa1.m2 = · · · = λan.ηB (h(f (a1, . . . , an))). (7) Sei Σ eine Signatur, h : A → B ein Σ-Homomorphismus, n > 0 und f : e1 × · · · × en → s ∈ Σ. Fu¨r alle m1, . . . , mn ∈ M (A), M (h)(m1 = λa1.m2 = · · · = λan.ηA(f A(a1, . . . , an))) B = M (h)(m1) = λb1.M (h)(m2) = · · · = λbn.ηB (f (b1, . . . , bn)). (8) Beweis von (5). M (h)(m = f ) (10) in Kap. 5 = = (m = f ) = ηB ◦ h m = λa.f (a) = ηB ◦ h (11) in Kap. 5 = (11) in Kap. 5 m = λa.M (h)(f (a)) = m = M (h) ◦ f. 138 Beweis von (6). M (h)(m) = f (10) in Kap. 5 = Def . M (h) = (m = ηB ◦ h) = f m = λa.ηB (h(a)) = f (9) in Kap. 5 = m = λa.f (h(a)) = m = f ◦ h. Beweis von (7). M (h)(m1 = λa1.m2 = · · · = λan.ηA(f (a1, . . . , an))) (5) = m1 = M (h) ◦ λa1.m2 = · · · = λan.ηA(f (a1, . . . , an)) = m1 = λa1.M (h)(m2 = · · · = λan.ηA(f (a1, . . . , an))) = ... = m1 = λa1.m2 = · · · = λan.M (h)(ηA(f (a1, . . . , an))) η ist nat. Transf . = m1 = λa1.m2 = · · · = λan.ηB (h(f (a1, . . . , an))). Beweis von (8). M (h)(m1 = λa1.m2 = · · · = λan.ηA(f A(a1, . . . , an))) (7) = m1 = λa1.m2 = · · · = λan.ηB (h(f A(a1, . . . , an))) h ist Σ−homomorph = m1 = λa1.m2 = · · · = λan.ηB (f B (h(a1), . . . , h(an))) 139 M (h)(m1) = λb1.M (h)(m2) = · · · = λbn.ηB (f B (b1, . . . , bn)) (6) = m1 = (λb1.M (h)(m2) = · · · = λbn.ηB (f B (b1, . . . , bn))) ◦ h = m1 = λa1.(λb1.M (h)(m2) = · · · = λbn.ηB (f B (b1, . . . , bn)))(h(a1)) = m1 = λa1.M (h)(m2) = λb2.M (h)(m3) = · · · = λbn.ηB (f B (h(a1), b2, . . . , bn)) = ... = m1 = λa1.m2 = λa2.m3 = · · · = λbn.ηB (f B (h(a1), h(a2), . . . , bn)) = ... = m1 = λa1.m2 = · · · = λan.ηB (f B (h(a1), . . . , h(an))) Symmetrie und Transitivit¨at von = liefern (8). o Satz 6.6 Sei T = TΣ(G). Der oben definierte LL-Compiler erfu¨llt A T compileA G = M (fold ) ◦ compileG (9) fu¨r alle Σ(G)-Algebren A und Plusmonaden M . Beweis. Wegen (2) folgt (9) aus den folgenden Gleichungen fu¨r die in obiger Definition von compileG verwendeten Hilfsfunktionen: 140 Fu¨r alle s ∈ SBZ und r ∈ R, A T transA s = M (fold × idX ∗ ) ◦ transs , tryrA A = M (fold × idX ∗ ) ◦ (10) tryrT . Beweis von (10) durch Noethersche Induktion bzgl. der im Beweis von Satz 6.4 definierten Ordnung . Fall 1: s ∈ BS. Fu¨r alle x ∈ X und w ∈ X ∗, M (fold A × idX ∗ )(transTs (xw)) Def . transTs = if x ∈ s then M (fold A × idX ∗ )(η(x, w)) else M (fold A × idX ∗ )(errmsg(xw)) η ist nat. Transf ., (1) = if x ∈ s then η((fold A × idX ∗ )(x, w)) else errmsg(xw) (fold A ×idX ∗ )(x,w)=(fold A (x),w)=(x,w) = Def . transA s = A transA s (xw), M (fold × Def . transA s = if x ∈ s then η(x, w) else errmsg(xw) Def . transTs T idX ∗ )(transs ()) = (1) M (fold A × idX ∗ )(errmsg()) = errmsg() transA s (). 141 Fall 2: s ∈ S. Um 6.5 (8) anwenden zu k¨onnen, definieren wir Σ als abstrakte Syntax einer Variante von G, in der jedes Terminalsymbol von G eine einelementige Basismenge ist, so dass der Konstruktor gr einer Regel r = (s → e1 . . . en) von G den Domain e1 ×· · ·×en → s hat. Außerdem erweitern wir die Mengen T × X ∗ und A × X ∗ zu Σ-Algebren derart, dass fold A × idX ∗ Σ-homomorph ist: Sei {j1, . . . , jk } = {1 ≤ j ≤ n | ej ∈ S ∪ BS}. Fu¨r alle B ∈ {T, A}, b1, . . . , bn ∈ B und w1, . . . , wn ∈ X ∗, ∗ grB×X ((b1, w1), . . . , (bn, wn)) =def (frB (bj1 , . . . , bjk ), wn). Fu¨r alle w ∈ X ∗, M (fold A × idX ∗ )(tryrT (w)) T do (t1, w1) ← transe1 (w); ... T Def . tryr A ) = M (fold × idX ∗ )( T (tn, wn) ← transen (wn−1); η(f T (t , . . . , t ), w ) j1 jk n r do (t1, w1) ← transTe1 (w); . .. A = M (fold × idX ∗ )( ) T (t , w ) ← trans (w ); n n n−1 en ∗ η(g T ×X ((t , w ), . . . , (t , w )) 1 1 n n r 142 6.5 (8) = A do (a , w ) ← M (fold × idX ∗ )(transTe1 (w)); 1 1 ... (an, wn) ← M (fold A × idX ∗ )(transTen (wn−1)); ∗ A×X η(gr ((a1, w1), . . . , (an, wn)) A T do (a1, w1) ← M (fold × idX ∗ )(transe1 (w)); . ∗ .. Def . grA×X = (an, wn) ← M (fold A × idX ∗ )(transTen (wn−1)); η(f A(a , . . . , a ), w ) j1 jk n r do (a1, w1) ← transA e1 (w); . .. Def . tryrA Induktionsvor . = tryrA(w) = (an, wn) ← transA en (wn−1 ); η(f A(a , . . . , a ), w ) j1 jk n r (11) und daher L Def . transTs T T M (fold )(transs (w)) = M (fold A)( m i=1 tryri (w)) (13) in Kap. 5 Lm (11) Lm Def . transA A A T = = s i=1 tryri (w) i=1 M (fold )(tryri (w)) = A transA s (w), 143 wobei r1, . . . , rm die Regeln von G mit linker Seite s sind. o Satz 6.7 Sei T = TΣ(G), W = Word (G) und M die Listen-, Mengen- oder Exceptionmonade. Der oben definierte LL-Compiler erfu¨llt [compileW G (w)](λv.v = w) (12) fu¨r alle w ∈ X ∗. (12) entspricht Gleichung (19) von Kapitel 5. Beweis. Wegen (2) folgt (12) aus den folgenden Gleichungen fu¨r die in obiger Definition von compileG verwendeten Hilfsfunktionen: Sei ϕ : X ∗ → (X ∗ × X ∗ → 2) w 7→ λ(v, v 0).vv 0 = w Fu¨r alle s ∈ SBZ, r ∈ R und w ∈ X ∗, W M (ϕ(w))(transW s (w)) = M (λp.1)(transs (w)), M (ϕ(w))(tryrW (w)) = M (λp.1)(tryrW (w)). (13) Beweis von (13) durch Noethersche Induktion bzgl. der im Beweis von Satz 6.4 definierten Ordnung . 144 Fall 1: s ∈ BS. Fu¨r alle x ∈ X und w ∈ X ∗, Def . transW W M (h(xw))(transs (xw)) = s M (h(xw))(if x ∈ s then η(x, w) else errmsg(xw)) = if x ∈ s then M (h(xw))(η(x, w)) else M (h(xw))(errmsg(xw)) η ist nat. Transf ., (1) = if x ∈ s then η2(xw = xw) else errmsg(xw) = if x ∈ s then η2(1) else errmsg(xw) η ist nat. Transf ., (1) = Def . transW s = if x ∈ s then M (λp.1)(η(x, w)) else M (λp.1)(errmsg(xw)) M (λp.1)(transW s (xw)), (1) Def . transW W = s M (h())(errmsg()) = errmsg() M (h())(transs ()) (1) Def . transW = M (λp.1)(errmsg()) = s M (λp.1)(transW s ()). Fall 2: s ∈ S und r = (s → e1 . . . en) ∈ R. Wir verwenden wie im Fall 2 des Beweises von Satz 6.6 neben fr die dort eingefu¨hrte Operation gr und erhalten fu¨r alle w ∈ X ∗: 145 Def . tryrW W = M (ϕ(w))(tryr (w)) M (ϕ(w))( do (v1, w1) ← transW e1 (w); ... (vn, wn) ← transW en (wn−1 ); W η(fr (vj1 , . . . , vjk ), wn) W do (v1, w1) ← transe1 (w); . ∗ .. Def . grW ×X = M (ϕ(w))( ) W (vn, wn) ← transen (wn−1); ∗ η(g W ×X ((v , w ) . . . , (v , w ))) 1 1 n n r do (v1, w1) ← transW e1 (w); . .. 6.5 (7) = (vn, wn) ← transW en (wn−1 ); ∗ η(ϕ(w)(g W ×X ((v , w ) . . . , (v , w )))) 1 1 n n r W do (v1, w1) ← transe1 (w); . ∗ .. Def . grW ×X = (vn, wn) ← transW en (wn−1 ); η(ϕ(w)(f W (v , . . . , v ), w )) j1 jk n r ) 146 Def . frW = do (v1, w1) ← transW e1 (w); ... (vn, wn) ← transW en (wn−1 ); η(ϕ(w))(v1 . . . vn, wn)) W do (v1, w1) ← transe1 (w); ... = (vn, wn) ← transW en (wn−1 ); η(v1 . . . vnwn = w) (14) Nach Induktionsvoraussetzung gilt W M (ϕ(w))(transW e1 (w)) = M (λp.1)(transe1 (w)) (15) W M (ϕ(wi))(transW ei+1 (wi )) = M (λp.1)(transei+1 (wi )) (16) und fu¨r alle 1 ≤ i < n. Da M die Listen-, Mengen- oder Exceptionmonade ist, erhalten wir (15) (v1w1 = w) = ϕ(w)(v1, w1) = 1 und (16) (vi+1wi+1 = wi) = ϕ(wi)(vi+1, wi+1) = 1 (17) (18) 147 fu¨r alle 1 ≤ i < n. Also ist (18) (18) (18) (17) v1 . . . vnwn = v1 . . . vn−1wn−1 = . . . = v1w1 = w (19) und daher do (v1, w1) ← transW e1 (w); ... W Def . try r M (λp.1)(tryrW (w)) = M (λp.1)( ) W (vn, wn) ← transen (wn−1); η(f W (v , . . . , v ), w ) j1 jk n r do (v1, w1) ← transW e1 (w); . ∗ . W ×X . Def . gr = M (λp.1)( (vn, wn) ← transW en (wn−1 ); ∗ η(g W ×X ((v , w ) . . . , (v , w ))) 1 1 n n r W do (v1, w1) ← transe1 (w); ... 6.5 (7) = (vn, wn) ← transW en (wn−1 ); η((λp.1)(g W ×X ∗ ((v , w ) . . . , (v , w )))) r 1 1 n n 148 ∗ Def . grW ×X = do (v1, w1) ← transW e1 (w); ... (vn, wn) ← transW en (wn−1 ); η(1) W do (v1, w1) ← transe1 (w); ... (19) (14) = = M (ϕ(w))(tryrW (w)) (vn, wn) ← transW en (wn−1 ); η(v . . . v w = w) 1 n n sowie (20) L Def . transW W T = s M (ϕ(w))( m M (ϕ(w))(transs (w)) i=1 tryri (w)) (13) in Kap. 5 Lm (20) Lm W W = M (ϕ(w))(try (w)) = ri i=1 i=1 M (λp.1)(tryri (w)) Def . transW = s M (λp.1)(transW s (w)), wobei r1, . . . , rm die Regeln von G mit linker Seite s sind. o 149 7 LR-Compiler LR-Compiler lesen ein Wort w wie LL-Compiler von links nach rechts, konstruieren dabei jedoch eine Rechtsreduktion von w, d.h. die Umkehrung einer Rechtsableitung. Da dies dem schrittweisen, mit den Bl¨attern beginnenden Aufbau eines Syntaxbaums entspricht, werden LR-Compiler auch bottom-up-Compiler genannt. ¨ Im Gegensatz zum LL-Compiler ist die entsprechende Ubersetzungsfunktion eines LRCompilers iterativ. Die Umkehrung der Ableitungsrichtung (Reduktion statt Ableitung) macht es m¨oglich, die Compilation durch einen Automaten, also eine iterativ definierte Funktion, zu steuern. W¨ahrend LL-Compiler keine linksrekursiven CFGs verarbeiten k¨onnen, sind LR-Compiler auf LR(k)-Grammatiken beschr¨ankt (s.u.). Sei G = (S, BS, Z, R) eine CFG, CS = BS ∪ Z und X = Z ∪ S BS. Wir setzen jetzt voraus, dass S das Symbol start enth¨alt und dieses nicht auf der rechten Seite einer Regel von R auftritt. 150 ¨ Ablauf der LR-Ubersetzung auf Ableitungsb¨ aumen start φ ε vw Ableitungsbäume Eingabe v w Ableitungsbäume Eingabe vw ε Ableitungsbaum Eingabe G heißt LR(k)-Grammatik, falls das Vorauslesen von k noch nicht verarbeiteten Eingabesymbolen genu¨gt, um zu entscheiden, ob ein weiteres Zeichen gelesen oder eine Reduktion durchgefu¨hrt werden muss, und, wenn ja, welche. Außerdem mu¨ssen die Basismengen von BS paarweise disjunkt sein. Beispiel 7.1 Die CFG G = ({S, A}, ∅, {∗, b}, {S → A, A → A ∗ A, A → b}) ist fu¨r kein k eine LR(k)-Grammatik. 151 Nach der Reduktion des Pra¨fixes b ∗ b des Eingabewortes b ∗ b ∗ b zu A ∗ A liegt ein shiftreduce-Konflikt vor. Soll man A ∗ A mit der Regel A → A ∗ A zu A reduzieren oder erst die Resteingabe ∗b lesen und dann b mit A → b zu A reduzieren? Die Resteingabe determiniert liefert keine Antwort. o first- und follow-Wortmengen Sei k > 0, α ∈ (S ∪ CS)∗ und s ∈ S. ∗ ∗ first k (α) = {β ∈ CS k | ∃ γ ∈ CS ∗ : α →G βγ} ∪ {β ∈ CS <k | α →G β} ∗ follow k (s) = {β ∈ CS k | ∃ α, γ ∈ CS ∗ : start →G αsβγ} ∪ ∗ {β ∈ CS <k | ∃ α ∈ CS ∗ : start →G αsβ} first(α) = first 1(α) follow (s) = follow 1(s) Simultane induktive Definition der first-Mengen ∈ first() C ∈ CS ∧ α ∈ (S ∪ CS)∗ ⇒ first(Cα) = {C} (s → α) ∈ R ∧ β ∈ (S ∪ CS)∗ ∧ C ∈ first(αβ) ⇒ C ∈ first(sβ) (1) (2) (3) 152 Sei G eine CFG. Wir definieren den LR(1)-Compiler fu¨r G in mehreren Schritten. Zun¨achst definieren wir einen Erkenner fu ¨ r die Sprache L(G)start. W¨ahrend Erkenner fu¨r regul¨are Sprachen regul¨are Ausdru¨cke auf Funktionen des Typs X ∗ → 2 abbilden (siehe Kapitel 2 und 3), bildet der folgende Erkenner (recognizer) W¨orter u¨ber den Sorten und Konstantenmengen von G auf jene Funktionen ab: recog1 : (S ∪ CS)∗ → 2X ∗ formuliert. Sei γ, α, ϕ ∈ (S ∪ CS)∗, x ∈ X und w ∈ X ∗. recog1(γα)(xw) = recog1(γαC)(w) falls ∃ s → αβ ∈ R, C ∈ CS, v ∈ (S ∪ CS)∗ : ∗ start →G γsv, β 6= , x ∈ C ∈ first(β) shift (read) recog1(γα)(w) = recog1(γs)(w) falls ∃ s → α ∈ R, v ∈ (S ∪ CS)∗ : ∗ start →G γsv, s 6= start, w = ∈ follow (s) ∨ head(w) ∈ follow (s) reduce recog1(ϕ)() = 1 falls start → ϕ ∈ R accept recog1(ϕ)(w) = 0 sonst reject 153 recog1 ist genau dann wohldefiniert, wenn G eine LR(1)-Grammatik ist. recog1 ist korrekt bzgl. L(G)start, d.h. fu¨r alle w ∈ X ∗ gilt: recog1()(w) = 1 ⇐⇒ w ∈ L(G)start (1) Die Funktion recog1 ist iterativ definiert. Um (1) ben¨otigt man daher eine Invariante. Sie lautet wie folgt: Fu¨r alle w ∈ X ∗, ∗ recog1(ϕ)(w) = 1 ⇐⇒ start →G ϕw. (2) (2) zeigt man durch Induktion u¨ber |w|. Aus (2) folgt sofort (1). Im zweiten Schritt wird das erste Argument von recog1 durch den Inhalt eines Zustandskellers ersetzt. Die zugrundeliegende endliche (!) Zustandsmenge Q ist die Bildmenge der Funktion state : (S ∪ CS)∗ → P(S × (S ∪ CS)∗ × (S ∪ CS)∗ × (1 + CS)) ∗ ϕ 7→ {(s, α, β, u) | ∃ γ, v : ϕ = γα, start →G γsv, s → αβ ∈ R, u = v = ∨ u = head(v) ∈ CS}. 154 Die Bedingungen in der Definition von recog1 werden durch Abfragen der Form (s, α, β, x) ∈ state(ϕ) ersetzt: recog1(γα)(xw) = recog1(γαC)(w) falls ∃ (s, α, β, ) ∈ state(γα), C ∈ CS : β 6= , x ∈ C ∈ first(β) recog1(γα)(w) = recog1(γs)(w) falls ∃ (s, α, , u) ∈ state(γα) : s 6= start, w = = u ∨ head(w) ∈ u ∈ CS recog1(ϕ)() = 1 falls (start, ϕ, , ) ∈ state(ϕ) recog1(ϕ)(w) = 0 sonst Der LR-Automat fu ¨ r G hat die Eingabemenge S ∪ CS, die Zustandsmenge QG = {state(ϕ) | ϕ ∈ (S ∪ CS)∗} und die (goto-Tabelle genannte) partielle (!) Transitionsfunktion δG : QG (→ QS∪CS G state(ϕ) 7→ λs.state(ϕs) 155 Simultane induktive Definition von QG und δG start → α ∈ R ⇒ (start, , α, ) ∈ q0 (s, α, s0β, u) ∈ q ∧ s0 → γ ∈ R ∧ v ∈ first(βu) ⇒ (s0, , γ, v) ∈ q (s, α, s0β, u) ∈ q, s0 ∈ S ∪ CS ⇒ (s, αs0, β, u) ∈ δG(q)(s0) (1) (2) (3) In der Definition von recog1 wird jedes Wort von (S ∪ CS)∗ durch eine gleichlange Liste von Zust¨anden des LR-Automaten ersetzt: h : (S ∪ CS)∗ → Q∗G 7→ q0 =def state() (s1, . . . , sn) 7→ (state(s1 . . . sn), state(s1 . . . sn−1), . . . , state(s1), state()) Im Folgenden benutzen wir gelegentlich die Haskell-Funktionen (:) und (++), um auf W¨ortern zu operieren (siehe Kapitel 9). ∗ Aus recog1 wird recog2 : Q∗G → 2X : Fu¨r alle q, qi ∈ QG, qs ∈ Q∗G, x ∈ X und w ∈ X ∗, 156 recog2(q : qs)(xw) = recog2(δG(q)(C) : q : qs, w) falls ∃ (s, α, β, ) ∈ q, C ∈ CS : β 6= , x ∈ C ∈ first(β) recog2(q1 : · · · : q|α| : q : qs)(w) = recog2(δG(q)(s) : q : qs)(w) falls ∃ (s, α, , u) ∈ q1 : s 6= start, w = = u ∨ head(w) ∈ u ∈ CS recog2(q : qs)() = 1 falls ∃ ϕ : (start, ϕ, , ) ∈ q recog2(qs)(w) = 0 sonst Offenbar gilt recog1 = recog2 ◦ h. Um die Suche nach bestimmten Elementen eines Zustands in den Iterationsschritten von recog2 zu vermeiden, verwenden wir die – Aktionstabelle fu ¨ r G genannte – Funktion actG : QG × (1 + CS) → R ∪ {shift, error }, die wie folgt definiert ist: 157 Fu¨r alle u ∈ 1 + CS, actG(q, u) = shift falls ∃ (s, α, β, ) ∈ q : β 6= , u ∈ first(β), s → α falls ∃ (s, α, , u) ∈ q, error sonst. Unter Verwendung von δG und actG erh¨alt man eine kompakte Definition von recog2: Fu¨r alle q, qi ∈ QG, qs ∈ Q∗G, x ∈ X und w ∈ X ∗, recog2(q : qs)(xw) = recog2(δG(q)(C) : q : qs)(w) falls ∃ C ∈ CS : x ∈ C, actG(q, C) = shift recog2(q1 : · · · : q|α| : q : qs)(w) = recog2(δG(q)(s) : q : qs)(w) falls ∃ u ∈ 1 + CS : actG(q1, u) = s → α, w = = u ∨ head(w) ∈ u ∈ CS recog2(q : qs)() = 1 falls actG(q, ) = start → α recog2(qs)(w) = 0 sonst 158 Beispiel 7.2 SAB2 G = ({S, A, B}, {c, d, +}, R) R = {S → A, A → A + B, A → B, B → c, B → d} LR-Automat fu¨r G q0 = {(S, , A, ), (A, , A + B, ), (A, , B, ), (A, , A + B, +), (A, , B, +), (B, , c, ), (B, , d, ), (B, , c, +), (B, , d, +)} q1 = δG(q0)(A) = {(S, A, , ), (A, A, +B, ), (A, A, +B, +)} q2 = δG(q0)(B) = {(A, B, , ), (A, B, , +)} q3 = δG(q0)(c) = {(B, c, , ), (B, c, , +)} = δG(q5)(c) q4 = δG(q0)(d) = {(B, d, , ), (B, d, , +)} = δG(q5)(d) q5 = δG(q1)(+) = {(A, A+, B, ), (A, A+, B, +), (B, , c, ), (B, , d, ), (B, , c, +), (B, , d, +)} q6 = δG(q5)(B) = {(A, A + B, , ), (A, A + B, , +)} 159 A B c d + q0 q1 q2 q3 q4 q1 q5 q5 q6 q3 q4 Aktionstabelle fu¨r G q0 q1 q2 q3 q4 q5 q6 shift error error error error shift error d shift error error error error shift error + error shift c A → B B → c B → d error A → A + B error S → A A → B B → c B → d error A → A + B 160 Ein Erkennerlauf recog2(q0)(c+d) = recog2(q3q0)(+d) wegen actG(q0, c) = shift und δG(q0)(c) = q3 = recog2(q2q0)(+d) wegen actG(q3, +) = B → c und δG(q0)(B) = q2 = recog2(q1q0)(+d) wegen actG(q2, +) = A → B und δG(q0)(A) = q1 = recog2(q5q1q0)(d) wegen actG(q1, +) = shift und δG(q1)(+) = q5 = recog2(q4q5q1q0)() wegen actG(q5, d) = shift und δG(q5)(d) = q4 = recog2(q6q5q1q0)() wegen actG(q4, ) = B → d und δG(q5)(B) = q6 = recog2(q1q0)() wegen actG(q6, ) = A → A + B und δG(q0)(A) = q1 = 1 wegen actG(q1, ) = S → A o Fu¨r den dritten und letzten Schritt zum LR(1)-Compiler ben¨otigen wir die abstrakte Syntax Σ(G) von G (siehe Kapitel 4). Sei A eine Σ(G)-Algebra. Aus recog2 wird der generische Compiler ∗ compileG : Q∗G × A∗ → M (A)X . 161 Er startet mit q0 = state() im ansonsten leeren Zustandskeller. Die Bedeutung der Liste von A-Elementen im Definitionsbereich von compileG entnimmt man am besten der folgenden Formalisierung der Korrektheit von compileG: • Fu¨r alle w ∈ X ∗ und t ∈ TΣ(G),start, compileG(q0, )(w) = η(fold A(t)) ⇒ fold Word (G)(t) = w, (1) Word (G) compileG(q0, )(w) = error(w) ⇒ w 6∈ L(G)start =def img(fold start ). (2) Die Invariante von compileG, aus der (1) folgt, ist komplizierter die des Erkenners recog1 (s.o.): • Fu¨r alle α = w0s1w1 . . . snwn mit wi ∈ Z ∗ und si ∈ S ∪ BS, qs ∈ Q∗G, ai ∈ A, w ∈ X ∗, und t ∈ TΣ(G)({x1, . . . , xn}), compileG(state(α) : qs, [a1, . . . , an])(w) = η(g1∗(t)) ⇒ g2∗(t) = αw, (3) wobei g1 und g2 Variablenbelegungen in A bzw. Word (G) sind, die xi auf ai bzw. si abbilden. Aus (3) ergibt sich die folgende iterative Definition von compileG: 162 Fu¨r alle n ∈ N, q, qi ∈ QG, qs ∈ Q∗G, ai ∈ A, as ∈ A∗, x ∈ X und w ∈ X ∗, compileG(q : qs, as)(xw) = compileG(δG(q)(C) : q : qs, as ++[x])(w) falls ∃ C ∈ CS : x ∈ C, actG(q, C) = shift compileG(q1 : · · · : qn : q : qs, as ++[ai1 , . . . , aik ])(w) = compileG(δG(q)(s) : q : qs, as ++[frA(ai1 , . . . , aik )])(w) falls ∃ x ∈ 1 + CS : actG(q1, x) = r = (s → e1 . . . en), {i1, . . . , ik } = {1 ≤ i ≤ n | ei ∈ S ∪ BS}, w = = x ∨ head(w) ∈ x ∈ CS compileG(q : qs, [ai1 , . . . , aik ])() = η(frA(ai1 , . . . , aik )) falls actG(q, ) = r = (start → e1 . . . en), {i1, . . . , ik } = {1 ≤ i ≤ n | ei ∈ S ∪ BS} compileG(qs, as)(w) = errmsg(w) sonst 163 Im Fall A = TΣ(G) bestehen die Listen as, [ai1 , . . . , aik ] und [frA(ai1 , . . . , aik )] aus Syntaxb¨aumen. Die zweite Gleichung zeigt ihren schrittweisen Aufbau: ai ai 1 k as fr ai 1 ai k as 164 Beispiel 7.3 SAB2 Hier ist der Parserlauf von Beispiel 7.2 erweitert um die schrittweise Konstruktion des Syntaxbaumes: compileG([0], )(c + d) = compileG([3, 0], [c])(+d) = compileG([2, 0], [fB→c])(+d) = compileG([1, 0], [fA→B (fB→c)])(+d) = compileG([5, 1, 0], [fA→B (fB→c), +])(d) = compileG([4, 5, 1, 0], [fA→B (fB→c), +, d])() = compileG([6, 5, 1, 0], [fA→B (fB→c), +, fB→d])() = compileG([1, 0], [fA→A+B (fA→B (fB→c), fB→d)])() = η(fS→A(fA→A+B (fA→B (fB→c), fB→d))) Link zur graphischen Darstellung dieses Parserlaufs Die Knoten der Syntaxb¨aume sind dort nicht mit Konstruktoren, sondern mit den Regeln markiert, aus denen sie hervorgehen, wobei der Pfeil zwischen der linken und rechten Seite einer Regel durch den Unterstrich ersetzt wurde. o 165 Beispiel 7.4 Auf den folgenden Seiten steht die vom C-Compiler-Generator yacc (“yet another compiler-compiler”) aus der folgenden LR(1)-Grammatik G erzeugte Zustandsmenge – deren Elemente hier nur aus den ersten drei Komponenten der o.g. Quadrupel bestehen – zusammen mit der auf die einzelnen Zust¨ande verteilten Eintr¨age der goto- und Aktionstabelle: $accept prog statems assign exp -> -> -> -> -> prog $end statems exp . statems assign ; | ID : = exp exp + exp | exp * exp | (exp) | NUMBER | ID So besteht z.B. der Zustand 0 aus den beiden Tripeln ($accept, , $end) und (statems, , ). Die beim Lesen eines Punktes im Zustand 0 ausgefu¨hrte Aktion ist eine Reduktion mit Regel 3, also mit statems → . Der Folgezustand von 0 ist bei Eingabe von prog bzw. statems der Zustand 1 bzw. 2. Link zur graphischen Darstellung des Parserlaufs auf dem Eingabewort ID : = NUM ; ID : = ID + NUM ; NUM * ID + ID . Den Sonderzeichen entsprechen in der input-Spalte des Parserlaufs und den u.a. Tabellen passende Wortsymbole. Die Knoten der Syntaxb¨aume sind wie in Beispiel 7.3 mit Regeln markiert. 166 167 168 Im Folgenden sind die goto- bzw. Aktionstabelle noch einmal getrennt wiedergegeben. op (open) und cl (close) bezeichnen eine ¨offnende bzw. schließende Klammer. end steht fu¨r das leere Wort . Wieder wird auf den Pfeil zwischen linker und rechter Seite einer Regel verzichtet. 169 170 171 Im Folgenden werden die Regeln von G um C-Code erweitert, der eine Σ(G)-Algebra implementiert, die dem Zustandsmodell von JavaLight (siehe 11.8) ¨ahnelt. 172 o 173 LR(k) LALR(k) SLR(k) LL(k) eindeutige kf. Gramm. A aAa|a nicht LR(k) S B E C D aB|eE Cc|Dd Cd|Dc b b LR(1), nicht LALR(1) S B C D Ba|aCd Cc|Dd b b LALR(1), nicht SLR(k) S A B C D E aA|bB Ca|Db Cc|Da E E ε LL(1), nicht LALR(k) Hierarchie der CFG-Klassen Zur Definition von LL(k)-Grammatiken verweisen wir auf die einschl¨agige Literatur. Kurz gesagt, sind das diejenigen nicht-linksrekursiven CFGs, deren LL-Parser ohne Backtracking auskommen. 174 eindeutige kontextfreie det. kf. Sprachen =LR(1)-Spr. =SLR(1)-Spr. =LALR(1)-Spr. LL(k)Spr. Sprachen Hierarchie der entsprechenden Sprachklassen Fu¨r jeden Grammatiktyp T bedeutet die Formulierung L ist eine T -Sprache lediglich, dass eine T -Grammatik existiert, die L erkennt. 175 Wa¨hrend der LL-Compiler von Kapitel 6 – nach Beseitigung von Linksrekursion – jede kontextfreie Grammatik verarbeitet, selbst dann, wenn sie mehrdeutig ist, zeigt die obige Grafik, dass die Forderung, dabei ohne Backtracking auszukommen, die Klasse der kompilierbaren ¨ Sprachen erheblich einschr¨ankt: Unter dieser Bedingung ist die bottom-up-Ubersetzung offenbar m¨achtiger als die top-down-Compilation. Umgekehrt w¨are es den Versuch wert (z.B. in Form einer Bachelorarbeit), in Anlehnung an den obigen Compiler fu¨r LR(1)-Grammatiken einen bottom-up-Compiler mit Backtracking zu entwickeln. Da die Determinismusforderung wegfiele, br¨auchten wir keinen Lookahead beim Verarbeiten der Eingabe, womit die Zusta¨nde generell nur aus Tripeln bestu¨nden – wie im Beispiel 7.4. 176 8 Haskell: Typen und Funktionen Die Menge 1 wird in Haskell unit-Typ genannt und mit () bezeichnet. () steht auch fu¨r das einzige Element von 1. Die Menge 2 entspricht dem Haskell-Typ Bool . Ihre beiden Elemente 0 und 1 werden mit False bzw. True bezeichnet. Alle Typen von Haskell sind aus Standardtypen wie Bool , Int oder F loat, Typvariablen sowie Typkonstruktoren wie × (Produkt), + (Summe oder disjunkte Vereinigung) oder → (Funktionsmenge) aufgebaut. Jeder Typ bezeichnet eine Menge, jeder Typkonstruktor eine Funktion auf Mengen von Mengen. Typnamen beginnen stets mit einem Großbuchstaben, Typvariablen mit einem Kleinbuchstaben. Typvariablen stehen fu¨r Mengen, Individuenvariablen fu¨r Elemente einer Menge. Beide mu¨ssen mit einem Kleinbuchstaben beginnen. Das (kartesische) Produkt A1 × · · · × An von Mengen A1, . . . , An wird in Haskell wie seine Elemente, die n-Tupel (a1, . . . , an), mit runden Klammern und Kommas notiert: (A1, . . . , An). Summen werden durch Haskell-Datentypen implementiert (siehe Kapitel 10). 177 Funktionen werden benannt und durch rekursive Gleichungen definiert (s.u.) oder unbenannt (anonym) als λ-Abstraktion λp.e (Haskell-Notation: \p -> e) dargestellt, wobei p ein Muster fu¨r die m¨oglichen Argumente (Parameter) von λp.e ist, z.B. p = (x, (y, z)). Muster bestehen aus Individuenvariablen und (Individuen-)Konstruktoren. Jede Variable kommt in einem Muster h¨ochstens einmal vor. Der “Rumpf” e der λ-Abstraktion λp.e ist ein aus beliebigen Funktionen und Variablen zusammengesetzter Ausdruck. Sei z.B. e = λ(x, y).x ∗ y + 5 + x. Die Auswertung einer Applikation von e wie z.B. e(7, 8), besteht in der Bildung der Instanz 7 ∗ 8 + 5 + 7 von e und deren anschließender Auswertung: e(7, 8) ; 7 ∗ 8 + 5 + 7 ; 56 + 5 + 7 ; 68 e hat den Typ A × A → A, wobei A ein beliebiger numerischer Typ, das ist ein Menge, auf der ∗ und + definiert sind. Folglich ist der Wert von e(7, 8) ein Element von A. Dementsprechend liefern die ghci-Befehle :type \(x,y)->x*y+5+x :type (\(x,y)->x*y+5+x)(7,8) die Typen Num a => (a,a) -> a bzw. Num a => a von e bzw. e(7, 8). 178 \ und -> sind offenbar die Haskell-Notationen des Symbols λ bzw. des Punktes einer λAbstraktion. Num ist die Typklasse fu¨r numerische Typen. Allgemein werden Typklassen in Kapitel 5 behandelt. Ein geschachelter λ-Ausdruck λp1.λp2. . . . .λpn.e kann in Haskell durch \p1 p2 . . . pn → e abgeku¨rzt werden. Er hat einen Typ der Form A1 → (A2 → . . . (An → B) . . . ), wobei auf diese Klammerung verzichtet werden kann, weil Haskell Funktionstypen automatisch rechtsassoziativ klammert. Anstelle der Angabe eines λ-Ausdrucks kann eine Funktion benannt und dann mit f mit Hilfe von Gleichungen definiert werden: f = \p -> e ist ¨aquivalent zu f p = e (applikative Definition) Funktionen, die andere Funktionen als Argumente oder Werte haben, heißen Funktionen h¨ oherer Ordnung. Der Typkonstruktor → ist rechtsassoziativ. Also ist die Deklaration (+) :: Int -> (Int -> Int) ist ¨aquivalent zu (+) :: Int -> Int -> Int Die Applikation einer Funktion ist linksassoziativ. Also ist ((+) 5) 6 ist ¨aquivalent zu (+) 5 6 179 (+) ist zwar als Pra¨fixfunktion deklariert, kann aber auch infix verwendet werden. Dann entfallen die runden Klammern um den Infixoperator: 5 + 6 ist ¨aquivalent zu (+) 5 6 Dasgleiche gilt fu¨r jede Funktion f eines Typs der Form A → B → C. Besteht f aus Sonderzeichen, dann wird f bei der Pr¨afixverwendung in runde Klammern gesetzt, bei der Infixverwendung nicht. Beginnt f mit einem Kleinbuchstaben und entha¨lt f keine Sonderzeichen, dann wird f bei der Infixverwendung in Akzentzeichen gesetzt, bei der Pr¨afixverwendung nicht: mod :: Int -> Int -> Int mod 11 5 ist ¨aquivalent zu 11 `mod` 5 Die Infixnotation wird auch verwendet, um die in f enthaltenen Sektionen (Teilfunktionen) des Typs A → C bzw. B → C zu benennen. Z.B. sind die folgenden Sektionen Funktionen des Typs Int -> Int, w¨ahrend (+) und mod den Typ Int -> Int -> Int haben. (+5) (9`mod`) ist ¨aquivalent zu ist ¨aquivalent zu (+) 5 mod 9 ist ¨aquivalent zu ist ¨aquivalent zu \x -> x+5 \x -> 9`mod`x 180 Der Applikationsoperator ($) :: (a -> b) -> a -> b f $ a = f a fu¨hrt die Anwendung einer gegebenen Funktion auf ein gegebenes Argument durch. Sie ist rechtsassoziativ und hat unter allen Operationen die niedrigste Priorit¨at. Daher kann durch Benutzung von $ manch schließende Klammer vermieden werden: f1 $ f2 $ ... $ fn a ; f1 (f2 (...(fn a)...))) Demgegenu¨ber ist der Kompositionsoperator (.) :: (b -> c) -> (a -> b) -> a -> c (g . f) a = g (f a) zwar auch rechtsassoziativ, hat aber – nach den Pr¨afixoperationen – die h¨ochste Priorit¨at. (f1 . f2 . ... . fn) a ; f1 (f2 (...(fn a)...))) U.a. benutzt man den Kompositionsoperator, um in einer applikativen Definition Argumentvariablen einzusparen: f a b ist ¨aquivalent zu g (h a) b 181 f a f f' a f' a f' ist ist ist ist ist ¨aquivalent ¨aquivalent ¨aquivalent ¨aquivalent ¨aquivalent zu zu zu zu zu g (h a) g . h g' . h' a (g'.) (h' a) (g'.) . h' Weitere polymorphe Funktionen h¨ oherer Ordnung id :: a -> a id a = a id = \a -> a Identit¨at const :: a -> b -> a const a b = a const a = \b -> a const = \a -> \b -> a konstante Funktion update :: Eq a => (a -> b) -> a -> b -> a -> b update f a b a' = if a == a' then b else f a' Funktionsupdate 182 ¨aquivalente Schreibweisen update(f) update f :: a -> b -> a -> b update f a :: b -> a -> b update(f)(a) (update f) a update f a b :: a -> b update(f)(a)(b) ((update f) a) b update f a b a' :: b update(f)(a)(b)(a') (((update f) a) b) a' flip :: (a -> b -> c) -> b -> a -> c flip f b a = f a b Vertauschung der Argumente flip mod 11 ist ¨aquivalent zu (`mod` 11) (s.o.) 183 curry :: ((a,b) -> c) -> a -> b -> c curry f a b = f (a,b) Kaskadierung (Currying) uncurry :: (a -> b -> c) -> (a,b) -> c uncurry f (a,b) = f a b Dekaskadierung lift :: (a -> b -> c) -> (state -> a) -> (state -> b) -> (state ->c) Operationslifting lift op f g state = f state `op` g state lift (,) (+3) (*3) 5 ; (8,15) lift (+) (+3) (*3) 5 ; 23 hoch :: (a -> a) -> Int -> a -> a Funktionsiteration f `hoch` 0 = if n == 0 then id else f . (f `hoch` (n-1)) ((+2)`hoch`5) 10 ; 20 184 Monomorphe und polymorphe Typen Ein Typ ohne Typvariablen heißt monomorph. Ein Typ mit Typvariablen wie z.B. a -> Int -> b heißt polymorph. Eine Funktion mit monomorphem oder polymorphem Typ heißt monomorphe bzw. polymorphe Funktion. Eine Funktion heißt mono- bzw. polymorph, wenn ihr Typ mono- bzw. polymorph ist. Ein Typ u heißt Instanz eines Typs t, wenn u durch Ersetzung der Typvariablen von t aus t entsteht. Typvariablen stehen fu¨r Mengen, Individuenvariablen stehen fu¨r Elemente einer Menge. Sie mu¨ssen ebenfalls mit einem Kleinbuchstaben beginnen. Eine besondere Individuenvariable ist der Unterstrich (auch Wildcard genannt). Er darf nur auf der linken Seite einer Funktionsdefinition vorkommen, was zur Folge hat, dass er fu¨r einen Teil eines Argumentmusters der gerade definierten Funktion steht, der ihre Werte an Stellen, die das Muster matchen, nicht beeinflusst (siehe Beispiele im na¨chsten Kapitel). 185 9 Haskell: Listen Sei A eine Menge. Die Menge A∗ (siehe Kapitel 2) wird in Haskell mit eckigen Klammern notiert: [A], ebenso wie ihre Elemente: Fu¨r (a1, . . . , an) ∈ A∗ schreibt man [a1, . . . , an]. Eine n-elementige Liste kann extensional oder als funktionaler Ausdruck dargestellt werden: [a1, . . . , an] ist ¨aquivalent zu a1 : (a2 : (. . . (an : []) . . . )) Die Konstante [] (leere Liste) vom Typ [a] und die Funktion (:) (Anfu¨gen eines Elements ans linke Ende einer Liste) vom Typ a → [a] → [a] heißen Konstruktoren, weil sie nicht wie andere Funktionen Werte berechnen, sondern dazu dienen, die Elemente eines Typs aufzubauen. Auch werden sie ben¨otigt, um die Zugeh¨origkeit eines Elementes eines Summentyps zu einem bestimmten Summanden wiederzugeben (siehe Kapitel 4). Die Klammern in a1 : (a2 : (. . . (an : []) . . . )) k¨onnen weggelassen werden, weil der Typ von (:) keine andere Klammerung zul¨asst. Die durch mehrere Gleichungen ausgedru¨ckten Fallunterscheidungen bei den folgenden Definitionen von Funktionen auf Listen ergeben sich aus verschiedenen Mustern der Funktionsargumente bzw. Bedingungen an die Argumente (Boolesche Ausdru¨cke hinter |). 186 Seien x, y, s Individuenvariablen. s ist ein Muster fu¨r alle Listen, [] das Muster fu¨r die leere Liste, [x] ein Muster fu¨r alle einelementigen Listen, x : s ein Muster fu¨r alle nichtleeren Listen, x : y : s ein Muster fu¨r alle mindestens zweielementigen Listen, usw. length :: [a] -> Int length (_:s) = length s+1 length _ = 0 length [3,44,-5,222,29] ; 5 Link zur schrittweisen Auswertung von length [3,44,-5,222,29] head :: [a] -> a head (a:_) = a head [3,2,8,4] ; 3 tail :: [a] -> [a] tail (_:s) = s tail [3,2,8,4] ; [2,8,4] (++) :: [a] -> [a] -> [a] (a:s)++s' = a:(s++s') _++s = s [3,2,4]++[8,4,5] ; [3,2,4,8,4,5] 187 (!!) :: [a] -> Int -> a (a:_)!!0 = a (_:s)!!n | n > 0 = s!!(n-1) [3,2,4]!!1 ; 2 init :: [a] -> [a] init [_] = [] init (a:s) = a:init s init [3,2,8,4] ; [3,2,8] last :: [a] -> a last [a] = a last (_:s) = last s last [3,2,8,4] ; 4 take take take take :: Int -> [a] -> [a] take 3 [3,2,4,8,4,5] ; [3,2,4] 0 _ = [] n (a:s) | n > 0 = a:take (n-1) s _ [] = [] drop drop drop drop :: Int -> [a] -> [a] drop 4 [3,2,4,8,4,5] ; [4,5] 0 s = s n (_:s) | n > 0 = drop (n-1) s _ [] = [] 188 Funktionslifting auf Listen map :: (a -> b) -> [a] -> [b] map f (a:s) = f a:map f s map _ _ = [] map (+1) [3,2,8,4] ; [4,3,9,5] map ($ 7) [(+1),(+2),(*5)] ; [8,9,35] map ($ a) [f1,f2,...,fn] ; [f1 a,f2 a,...,fn a] zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] zipWith f (a:s) (b:s') = f a b:zipWith f s s' zipWith _ _ _ = [] zipWith (+) [3,2,8,4] [8,9,35] ; [11,11,43] zip :: [a] -> [b] -> [(a,b)] zip = zipWith (,) zip [3,2,8,4] [8,9,35] ; [(3,8),(2,9),(8,35)] 189 Strings sind Listen von Zeichen Strings werden als Listen von Zeichen betrachtet, d.h. die Typen String und [Char] sind identisch: "Hallo" = ['H','a','l','l','o'] Deshalb sind alle Listenfunktionen auch auf Strings anwendbar. words :: String -> [String] und unwords :: [String] -> String zerlegen bzw. konkatenieren Strings, wobei Leerzeichen, Zeilenumbru¨che ('\n') und Tabulatoren ('\t') als Trennsymbole fungieren. unwords fu¨gt Leerzeichen zwischen die zu konkatenierenden Strings. lines :: String -> [String] und unlines :: [String] -> String zerlegen bzw. konkatenieren Strings, wobei nur Zeilenumbru¨che als Trennsymbole fungieren. unlines fu¨gt '\n' zwischen die zu konkatenierenden Strings. 190 Listenfaltung Faltung einer Liste von links her f f f foldl f a [b1,b2,b3,b4,b5] f f a b1 b2 foldl :: (a -> b -> a) -> a -> [b] -> a foldl f a (b:s) = foldl f (f a b) s foldl _ a _ = a b3 b4 b5 a ist Zustand, b ist Eingabe f ist Zustandsu ¨berfu ¨hrung foldl1 :: (a -> a -> a) -> [a] -> a foldl1 f (a:s) = foldl f a s sum and minimum concat = = = = foldl (+) 0 foldl (&&) True foldl1 min foldl (++) [] product or maximum = foldl (*) 1 = foldl (||) False = foldl1 max 191 concatMap :: (a -> [b]) -> [a] -> [b] concatMap f = concat . map f Parallele Faltung zweier Listen von links her f f fold2 f a [b1,b2,b3,b4,b5] [c1,c2,c3,c4,c5] f f f a b1 c1 b2 c2 b3 c3 b4 c4 b5 c5 fold2 :: (a -> b -> c -> a) -> a -> [b] -> [c] -> a fold2 f a (b:s) (c:s') = fold2 f (f a b c) s s' fold2 _ a _ _ = a listsToFun :: Eq a => b -> [a] -> [b] -> a -> b listsToFun = fold2 update . const 192 Beginnend mit const b, erzeugt listsToFun b schrittweise aus einer Argumentliste as und einer Werteliste bs die entsprechende Funktion: ( bs!!i falls i = max{k | as!!k = a, k < length(bs)}, listsToFun b as bs a = b sonst. Faltung einer Liste von rechts her Entspricht der Auswertung ihrer Konstruktordarstellung in einer Algebra (siehe Kapitel 2). foldr f a [b1,b2,b3,b4,b5] f : f f : f f : f f : f f b 1 b 2 b 3 b 4 b 5 : a b 1 b 2 b 3 b 4 b 5 f [] a foldr :: (b -> a -> a) -> a -> [b] -> a foldr f a (b:s) = f b $ foldr f a s foldr _ a _ = a 193 Der Applikationsoperator $ als Parameter von Listenfunktionen foldr ($) a [f1,f2,f3,f4] ; f1 $ f2 $ f3 $ f4 a foldl (flip ($)) a [f4,f3,f2,f1] ; f1 $ f2 $ f3 $ f4 a map f [a1,a2,a3,a4] map ($a) [f1,f2,f3,f4] ; ; [f a1,f a2,f a3,f a4] [f1 a,f2 a,f3 a,f4 a] zipWith ($) [f1,f2,f3,f4] [a1,a2,a3,a4] ; [f1 a1,f2 a2,f3 a3,f4 a4] 194 Listenlogik any :: (a -> Bool) -> [a] -> Bool any f = or . map f any (>4) [3,2,8,4] ; True all :: (a -> Bool) -> [a] -> Bool all f = and . map f all (>2) [3,2,8,4] ; False elem :: Eq a => a -> [a] -> Bool elem a = any (a ==) elem 2 [3,2,8,4] ; True notElem :: Eq a => a -> [a] -> Bool notElem a = all (a /=) notElem 9 [3,2,8,4] ; True filter :: (a -> Bool) -> [a] -> [a] filter f (a:s) = if f a then a:filter f s else filter f s filter f _ = [] filter (<8) [3,2,8,4] ; [3,2,4] 195 Jeder Aufruf von map, zipWith, filter oder einer Komposition dieser Funktionen entspricht einer Listenkomprehension: map f s = [f a | a <- s] zipWith f s s' = [f a b | (a,b) <- zip s s'] filter f s = [a | a <- s, f a] zip(s)(s’) ist nicht das kartesische Produkt von s und s’. Dieses entspricht der Komprehension [(a,b) | a <- s, b <- s’]. Allgemeines Schema von Listenkomprehensionen: [e(x1, . . . , xn) | x1 ← s1, . . . , xn ← sn, be(x1, . . . , xn)] :: [a] • x1, . . . , xn sind Variablen, • s1, . . . , sn sind Listen, • e(x1, . . . , xn) ist ein Ausdruck des Typs a, • xi ← si heißt Generator und steht fu¨r xi ∈ si, • be(x1, . . . , xn) heißt Guard und ist ein Boolescher Ausdruck. Jede endlichstellige Relation l¨asst sich als Listenkomprehension implementieren, z.B. die Menge aller Tripel (a, b, c) ∈ A1 × A2 × A3, die ein Pr¨adikat p : A1 × A2 × A3 → Bool erfu¨llen, durch [(a,b,c) | a <- a1, b <- a2, c <- a3, p(a,b,c)]. 196 10 Haskell: Datentypen und Typklassen Zun¨achst das allgemeine Schema einer Datentypdefinition: data DT a_1 ... a_m = Constructor_1 typ_11 ... typ_1n_1 | ... | Constructor_k typ_k1 ... typ_kn_k typ11, . . . , typknk sind beliebige Typen, die außer a1, . . . , am keine Typvariablen enthalten. DT heißt rekursiv, wenn DT in mindestens einem dieser Typen vorkommt. Die durch DT implementierte Menge besteht aus allen Ausdru¨cken der Form Constructor_i e_1 ... e_n_i, wobei 1 ≤ i ≤ n und fu¨r alle 1 ≤ j ≤ ni ej ein Element des Typs typij ist. Als Funktion hat Constructori den Typ typ_i1 -> ... -> typ_in_i -> DT a_1 ... a_m. Alle mit einem Großbuchstaben beginnenden Funktionssymbole und alle mit einem Doppelpunkt beginnenden Folgen von Sonderzeichen werden vom Haskell-Compiler als Konstruktoren eines Datentyps aufgefasst und mu¨ssen deshalb irgendwo im Programm in einer Datentypdefinition vorkommen. 197 Die Unterschiede in der Schreibweise von Konstruktoren bei Infix- bzw. Pra¨fixverwendung sind dieselben wie bei anderen Funktionen. Beispiel 10.1 Listen als selbstdefinierter Datentyp data ListT x = Nil | Cons x (ListT x) Semantisch entspricht List x dem im vorigen Kapitel behandelten Typ [x] der Listen mit Elementen von x. So sind z.B. [1,88,5] :: [Int] und Cons 1 $ Cons 88 $ Cons 5 Nil :: List Int semantisch ¨aquivalent. Die durch List a implementierte Menge besteht also aus Nil und allen Ausdru¨cken der Form Cons x_1 $ ... $ Cons x_n Nil, wobei x1, . . . , xn Elemente einer Instanz X der Typvariablen x sind. ListT x l¨ost die Gleichung M = {Nil } + {Cons(x)(t) | x ∈ X, t ∈ M } (1) in der Mengenvariablen M . 198 Da die Gleichungen, aus denen sich die Haskell-Definition eines Objekts, einer Funktion oder hier: einer Menge zusammensetzt, L¨osungen haben, definieren sie tats¨achlich ein Objekt, eine Funktion bzw. eine Menge. Allerdings ist die Lo¨sung nicht immer eindeutig. Im Fall von Haskell-Datentypen gibt es in der Regel eine kleinste und/oder eine gr¨oßte L¨osung (bzgl. Mengeninklusion). So bilden die endlichen aus Nil, Cons und Elementen von A gebildeten Ausdru¨cke die kleinste L¨osung von (1) und damit eine Haskell-Implementierung der initialen List(X)-Algebra TList(X) (siehe 2.1). Die gr¨oßte L¨osung von (1) enth¨alt zus¨atzlich alle unendliche Ausdru¨cke der Form Cons x_1 $ Cons x_2 $ Cons x_3 $ Cons x_4 $ ... (∗) Z.B. hat die Haskell-Definitionsgleichung blink = Cons 0 $ Cons 1 blink die L¨osung Cons 0 $ Cons 1 $ Cons 0 $ Cons 1 $ ... o Wenn alle Konstruktoren eines Datentyps DT mindestens ein Argument vom Typ DT haben, dann sind alle seine Elemente unendlich. 199 Entfernt man z.B. Nil aus dem Datentyp ListT x, dann besteht die gro¨ßte Lo¨sung der zugeh¨origen Gleichung M = {Cons(x)(t) | x ∈ X, t ∈ M } (2) aus allen unendlichen Ausdru¨cken der Form (∗) und ist damit eine Haskell-Implementierung der finalen Stream(X)-Algebra coTStream(X) (siehe Beispiel 10.3 und Kapitel 20). Beispiel 10.2 (siehe 2.1) Die Menge der Reg(CS)-Grundterme lo¨st die Gleichung M = {eps, mt} + {con(C) | C ∈ CS} + {par(t, u) | t, u ∈ M } + {seq(t, u) | t, u ∈ M } + {iter(t) | t ∈ M } (3) in der Mengenvariablen M . Die initiale Reg(CS)-Algebra TReg(CS) wird deshalb durch folgenden Datentyp implementiert: data RegT cs = Eps | Mt | Con cs | Par (RegT cs) (RegT cs) | Seq (RegT cs) (RegT cs) | Iter (RegT cs) Eps, Mt, Con, Par, Seq und Iter implementieren also die Interpretationen der gleichnamigen Konstruktoren von Reg(CS) in TReg(CS). o 200 Dementsprechend l¨ost der obige Datentyp M = DT (a1) . . . (an) die Gleichung M = {Constructor1 (t11) . . . (t1n1 ) | t11 ∈ typ11, . . . , t1n1 ∈ typ1n1 } + · · · + {Constructork (tk1) . . . (tknk ) | tk1 ∈ typk1, . . . , tknk ∈ typknk }. (4) Attributierte Datentypen Um auf die Argumente eines Konstruktors zugreifen zu k¨onnen, ordnet man ihnen Namen zu, die Attribute oder field labels genannt werden. In der obigen Definition von DT wird Constructor_i typ_i1 ... typ_in_i erweitert zu: Constructor_i {attr_i1 :: typ_i1, ..., attr_in_i :: typ_in_i}. Wie Constructori , so ist auch attrij eine Funktion. Als solche hat sie den Typ DT a1 ... am -> typ_ij. Attribute sind invers zu Konstruktoren: Der Ausdruck attr_ij (Constructor_i e_1 ... e_ni). 201 hat den Wert ej . Beispiel Punkte im Raum data Point = Point {x,y,z :: Float} o Beispiel Listen mit Attributen data ListT x = Nil | Cons {hd :: x, tl :: ListT x} Hier sind die Attribute hd :: List x -> x und tl :: List x -> List x partielle Funktionen. Semantisch entsprechen sie den Standardfunktionen head :: [x] -> x bzw. tail :: [x] -> [x] o Beispiel 10.3 Str¨ ome als Coterme Der rekursive Datentyp data StreamCot x = Cons {hd :: x, tl :: StreamCot x} 202 l¨ost die obige Gleichung (2) in M . Hier sind die Attribute hd :: StreamCot x -> x und tl :: StreamCot x -> StreamCot x totale Funktionen. Sie implementieren die Interpretation der Destruktoren head : list → X bzw. tail : list → list von Stream(X) (siehe 2.2) in der finalen Stream(X)-Algebra coTStream(X), deren Tr¨agermenge fu¨r list aus allen unendlichen B¨aumen (Cotermen) der Form ε head tail x1 ε tail head x2 ε head x3 tail ε besteht. hd(t) bzw. tl(t) liefert den Unterbaum von t, auf den die mit head bzw. tail markierte Kante zeigt (siehe Kapitel 20). 203 Mit Attributen lautet die obige Definitionsgleichung fu¨r den Strom blink wie folgt: blink :: StreamCot Int blink = Cons 0 (Cons 1 blink) Diese Gleichung implementiert eine Unteralgebra von coTStream(Z) mit genau zwei Elementen: blink und Cons(1)(blink). o Die aus vielen Programmiersprachen bekannten Recordtypen und Objektklassen lassen sich als Datentypen mit genau einem Konstruktor, aber mehreren Attributen, implementieren. In objektorientierten Sprachen schreibt man in der Regel x.attr ij anstelle von attr ij(x). Mit Attributen lautet das allgemeine Schema einer Datentypdefinition also wie folgt: data DT a_1 ... a_m = Constructor_1 {attr_11 :: typ_11, ..., attr_1n_1 :: typ_1n_1} | ... | Constructor_k {attr_k1 :: typ_k1, ..., attr_kn_k :: typ_kn_k} Elemente von DT k¨onnen mit oder ohne Attribute definiert werden: obj = Constructor_i e_i1 ... e_in_i ist ¨aquivalent zu obj = Constructor_i {attr_i1 = e_i1, ..., attr_in_i = e_in_i} 204 Die Werte einzelner Attribute von obj ko¨nnen wie folgt vera¨ndert werden: obj' = obj {attr_ij_1 = e_1', ..., attr_ij_m = e_m'} obj 0 unterscheidet sich von obj dadurch, dass den Attributen attr_ij_1, ...,attr_ij_m die Werte e01, . . . , e0m zugewiesen wurden. Attribute du¨rfen nicht rekursiv definiert werden. Folglich deutet der Haskell-Compiler jedes Vorkommen von attrij auf der rechten Seite einer Definitionsgleichung als eine vom gleichnamigen Attribut verschiedene Funktion und sucht nach deren Definition. Dies kann man nutzen, um attrij doch rekursiv zu definieren, indem in der zweiten Definition von obj (s.o.) die Gleichung attrij = ej durch attrij = attrij ersetzt und attrij lokal definiert wird: obj = Constructor_i {attr_i1 = e_1, ..., attr ij = attr_ij, ..., attr_in_i = e_n_i} where attr_ij = ... oder where attr_ij x = ... falls attr_ij eine Funktion ist 205 Ein Konstruktor darf nicht zu mehreren Datentypen geho¨ren. Ein Attribut darf nicht zu mehreren Konstruktoren unterschiedlicher Datentypen geh¨oren. Beispiel 10.4 Moore-Automaten als Coterme Der rekursive Datentyp data DAutCot x y = State {next :: x -> DAutCot x y, out :: y} l¨ost die Gleichung M = {State(f )(y) | f : X → M , y ∈ Y } in der Mengenvariablen M . Die Attribute next :: DAutCot x y -> x -> DAut x y und out :: DAutCot x y -> y implementieren die Interpretation der Destruktoren δ : state → stateX bzw. β : state → Y von DAut(X, Y ) (siehe 2.2) in der finalen DAut(X, Y )-Algebra coTDAut(X,Y ), deren Tr¨agermenge fu¨r state aus allen unendlichen B¨aumen (Cotermen) der Form 206 ε δx1 ε δx1 δx2 ε β y2 δx3 ε δx3 δx2 β ε ε ε y1 β δx1 ε y3 β δx2 δx 3 δx1 ε δx2 δx3 ε ε y4 ε ε besteht (siehe Kapitel 20). next(t)(x) und out(t) liefern den Unterbaum von t, auf den die mit δx bzw. β markierte Kante zeigt (siehe Kapitel 20). Analog zum Strom blink in Beispiel 10.3 definieren wir Elemente von coTDAut(X,Y ) durch rekursive Gleichungen, z.B.: esum,osum :: DAutCot Int Bool esum = State (\x -> if even x then esum else osum) True osum = State (\x -> if even x then osum else esum) False Diese Gleichungen implementieren eine Unteralgebra A von coTAcc(Z) mit genau zwei Elementen: esum und osum. o 207 Typklassen stellen Bedingungen an die Instanzen einer Typvariablen. Die Bedingungen bestehen in der Existenz bestimmter Funktionen, z.B. class Eq a where (==), (/=) :: a -> a -> Bool (/=) = (not .) . (==) Eine Instanz einer Typklasse besteht aus den Instanzen ihrer Typvariablen sowie Definitionen der in ihr deklarierten Funktionen. instance Eq (Int,Bool) where (x,b) == (y,c) = x == y && b == c instance Eq a => Eq [a] where s == s' = length s == length s' && and $ zipWith (==) s s' Auch (/=) k¨onnte hier definiert werden. Die Definition von (/=) in der Klasse Eq als Negation von (==) ist nur ein Default! Der Typ jeder Funktion einer Typklasse muss die - in der Regel eine - Typvariable der Typklasse mindestens einmal enthalten. Sonst w¨are die Funktion ja gar nicht von (der jeweiligen Instanz) der Typvariable abh¨angig. 208 10.5 Mengenoperationen auf Listen insert :: Eq a => a -> [a] -> [a] insert a s@(b:s') = if a == b then s else b:insert a s' insert a _ = [a] remove :: Eq a => a -> [a] -> [a] remove = filter . (/=) union :: Eq a => [a] -> [a] -> [a] union = foldl $ flip insert Mengenvereinigung diff :: Eq a => [a] -> [a] -> [a] diff = foldl $ flip remove Mengendifferenz inter :: Eq a => [a] -> [a] -> [a] inter s = filter (`elem` s) Mengendurchschnitt unionMap :: Eq a => (a -> [b]) -> [a] -> [b] unionMap f = foldl union [] . map f concatMap fu ¨r Mengen 209 subset :: Eq a => [a] -> [a] -> Bool s `subset` s' = all (`elem` s') s Mengeninklusion eqset :: Eq a => [a] -> [a] -> Bool s `eqset` s' = s `subset` s' && s' `subset` s Mengengleichheit o Unterklassen Typklassen k¨onnen wie Objektklassen andere Typklassen erben. Die jeweiligen Oberklassen werden vor dem Erben vor dem Pfeil => aufgelistet. class Eq a => Ord a where (<=), (<), (>=), (>) :: a -> a -> Bool max, min :: a -> a -> a a < b = a <= b && a /= b a >= b = b <= a a > b = b < a max x y = if x >= y then x else y min x y = if x <= y then x else y 210 Beispiel 10.6 Quicksort quicksort :: Ord a => [a] -> [a] quicksort (x:s) = quicksort (filter (<= x) s)++x: quicksort (filter (> x) s) quicksort s = s o Ausgeben Vor der Ausgabe von Daten eines Typs T wird automatisch die T-Instanz der Funktion show aufgerufen, die zur Typklasse Show a geh¨ort. class Show a where show :: a -> String show x = shows x "" shows :: a -> String -> String shows = showsPrec 0 showsPrec :: Int -> a -> String -> String 211 Das String-Argument von showsPrec wird an die Ausgabe des Argumentes vom Typ a angefu¨gt. Steht deriving Show am Ende der Definition eines Datentyps, dann werden dessen Elemente in der Darstellung ausgegeben, in der sie im Programmen vorkommen. Fu¨r andere Ausgabeformate mu¨ssen entsprechende Instanzen von show oder showsPrec definiert werden. Beispiel 10.7 Regul¨ are Ausdru ¨ cke ausgeben Eine der Syntax von Reg(CS) entsprechende Ausgabe von Reg(CS)-Termen (siehe Beispiel 10.2) liefert die folgende Instanz von Show. Der ganzzahlige Wert von showReg ist die Priorit¨at des jeweils fu¨hrenden regul¨aren Operators. Seine Berechnung erlaubt den Verzicht auf u¨berflu¨ssige Klammern: instance Show cs => Show (RegT cs) where show = fst . showReg showReg :: Show cs => RegT cs -> (String,Int) showReg t = case t of Eps -> ("eps", 3) Mt -> ("mt", 3) Con c -> (show c, 3) Par e e' -> (str e ++ '+':str e', 0) 212 Seq e e' -> (enclose (str e) (prio e) 1 ++ '.': enclose (str e') (prio e') 1, 1) Iter e -> (enclose (str e) (prio e) 2 ++ "*", 2) where str = fst . showReg prio = snd . showReg enclose str p q = if p < q then '(':str++")" else str Einlesen Bei der Eingabe von Daten eines Typs T wird automatisch die T-Instanz der Funktion read aufgerufen, die zur Typklasse Read a geh¨ort: class Read a where readsPrec :: Int -> String -> [(a,String)] reads :: String -> [(a,String)] reads = readsPrec 0 read :: String -> a read s = case [x | (x,t) <- reads s, ("","") <- lex t] of 213 [x] -> x [] -> error "PreludeText.read: no parse" _ -> error "PreludeText.read: ambiguous parse" reads s liefert eine Liste von Paaren, bestehend aus dem als Element vom Typ a erkannten Pra¨fix von s und der jeweiligen Resteingabe (= Suffix von s). lex :: String -> [(a,String)] ist eine Standardfunktion, die ein evtl. aus mehreren Zeichen bestehendes Symbol erkennt, vom Eingabestring abspaltet und sowohl das Symbol als auch die Resteingabe ausgibt. Der Generator ("","") <- lex t in der obigen Definition von read s bewirkt, dass nur die Paare (x,t) von reads s beru¨cksichtigt werden, bei denen die Resteingabe t aus Leerzeichen, Zeilenumbru¨chen und Tabulatoren besteht. Steht deriving Read am Ende der Definition eines Datentyps, dann werden dessen Elemente in der Darstellung erkannt, in der sie in Programmen vorkommen. Fu¨r andere Eingabeformate mu¨ssen entsprechende Instanzen von readsPrec definiert werden. Jede Instanz von readsPrec ist – im Sinne von Kapitel 5 – ein Parser in die Listenmonade. Da die Implementierung von Parsern und Compilern in Haskell erst in sp¨ateren Kapiteln behandelt wird, geben wir an dieser Stelle keine Beispiele fu¨r readsPrec an. 214 11 Algebren in Haskell Sei Σ = (S, BS, ∅, F ) eine Signatur, S = {s1, . . . , sm} und F = {fi : ei → e0i | 1 ≤ i ≤ n}. Jede Σ-Algebra entspricht einem Element des folgenden polymorphen Datentyps: data Sigma s_1 ... s_m = Sigma {f_1 :: e_1 -> e_1', ..., f_n :: e_n -> e_n'} Die Sorten und Konstruktoren von Σ werden also durch Typvariablen bzw. Attribute wiedergegeben und durch die Tr¨agermengen bzw. kaskadierten Operationen der jeweiligen Algebra instanziiert. Um eine Signatur Σ in Haskell zu implementieren, genu¨gt es daher, den Datentyp ihrer Algebren nach obigen Schema zu formulieren. Jede Σ-Algebra ist ein Element dieses Datentyps. Er beschreibt also im Gegensatz zu den Datentypen der Beispiele 10.1-10.4 nicht eine einzelne Algebra, sondern eine ganze Klasse von Algebren. 215 Beispiel 11.1 Datentyp der List(X)-Algebren data List x list = List {nil :: list, cons :: x -> list -> list} Damit kann die List(X)-Algebra TList(X) (siehe Beispiel 10.1) wie folgt implementiert werden: listT :: List x (ListT x) listT = List Nil Cons Eine weitere List(X)-Algebra ist durch den Haskell-Standardtyp fu¨r Listen gegeben: listH :: List x [x] listH = List [] (:) Die folgende Funktion implementiert die Faltung fold A : TList(X) → A von List(X)Grundtermen in beliebigen List(X)-Algebren A: foldList :: List x list -> ListT x -> list foldList alg Nil = nil alg foldList alg (Cons x t) = cons alg x $ foldList alg t Z.B wertet foldList listH List(X)-Terme in listH aus. o 216 Beispiel 11.2 Datentyp der Reg(CS)-Algebren data Reg cs reg = Reg {eps,mt :: reg, con :: cs -> reg, par,seq_ :: reg -> reg -> reg, iter :: reg -> reg} Damit kann die Reg(CS)-Algebra TReg(CS) (siehe Beispiel 10.2) wie folgt implementiert werden: regT :: cs -> Reg cs (RegT cs) regT cs = Reg Eps Mt Con Var Par Seq Iter Fu¨r konstruktive Signaturen Σ entspricht jede induktiv definierte Funktion f : TΣ → A der Faltung fold A von Σ-Grundtermen in der zu einer Σ-Algebra erweiterten Menge A. So ist z.B. die Ausgabefunktion showReg fu¨r regul¨are Ausdru¨cke von Beispiel 10.7 die Faltung von Reg(CS)-Termen in folgender Reg(CS)-Algebra regWord mit Tr¨agermenge String × Z: regWord :: Show cs => Reg cs (String,Int) regWord = Reg eps mt con par seq_ iter where eps = ("eps", 3) mt = ("mt", 3) con c = (show c, 3) par (str1,_) (str2,_) = (str1 ++ '+':str2, 0) 217 seq_ (str1,p1) (str2,p2) = (enclose str1 p1 1 ++ '.': enclose str2 p2 1, 1) iter (str,p) = (enclose str p 2 ++ "*", 2) enclose str p q = if p < q then '(':str++")" else str regWord stimmt nicht mit der Reg(CS)-Algebra Regword von Abschnitt 2.6 u¨berein. Die Faltung in Regword beru¨cksichtigt nicht die unterschiedlichen Priorit¨aten regul¨arer Operatoren und erzeugt deshalb auch u¨berflu¨ssige Klammern. Die folgende Funktion implementiert die Faltung fold A : TReg(CS) → A von Reg(CS)Grundtermen in beliebigen Reg(CS)-Algebren A: foldReg :: Reg cs reg -> RegT cs -> reg foldReg alg t = case t of Eps -> eps alg Mt -> mt alg Con c -> con alg c Var x -> var alg x Par t u -> par alg (out t) $ out u Seq t u -> seq_ alg (out t) $ out u Iter t -> iter alg $ out t where out = foldReg alg Offenbar stimmt fst . foldReg regWord mit showReg u¨berein. o 218 Beispiel 11.3 Datentyp der Stream(X)-Algebren data Stream x list = Stream {head :: list -> x, tail :: list -> list} Damit kann die Stream(X)-Algebra coTStream(X) (siehe Beispiel 10.3) wie folgt implementiert werden: streamCot :: Stream x (StreamCot x) streamCot = Stream hd tl Eine Stream(Z)-Algebra (siehe Beispiel 10.3): data A = Blink | Blink' blinkT :: Stream Int A blinkT = Stream head tail where head head tail tail Blink Blink' Blink Blink' = = = = 0 1 Blink' Blink 219 Die finale Stream(X)-Algebra (mit Tr¨agermenge) X N (siehe Beispiel 17.5) kann wie folgt implementiert werden: streamF :: Stream x (Int -> x) streamF = Stream (\f -> f 0) (\f n -> f $ n+1) Fu¨r alle Stream(X)-Algebren A implementieren folgende Funktionen die Entfaltungen von Elementen von A zu Funktionen auf N bzw. Stream(X)-Cotermen (siehe 2.9 bzw. Kapitel 20): unfoldStreamF :: Stream x list -> list -> Int -> x unfoldStreamF alg s 0 = head alg s unfoldStreamF alg s n = unfoldStreamF alg (tail alg s) $ n-1 unfoldStream :: Stream x list -> list -> StreamCot x unfoldStream alg s = Cons (head alg s) (unfoldStream alg $ tail alg s) o 220 Beispiel 11.4 Datentyp der DAut(X, Y )-Algebren data DAut x y state = DAut {delta :: state -> x -> state, beta :: state -> y} Damit kann die DAut(X, Y )-Algebra coTDAut(X,Y ) (siehe Beispiel 10.4) wie folgt implementiert werden: dAutCot :: DAut x y (DAutCot x y) dAutCot = DAut next out Eine Acc(Z)-Algebra (siehe Beispiel 10.4): data A = Esum | Osum eo :: DAut Int Bool A eo = DAut delta beta where delta Esum x = if even x then Esum else Osum delta Osum x = if even x then Osum else Esum beta Esum = True beta Osum = False ∗ Die finale DAut(X, Y )-Algebra Beh(X, Y ) mit Tr¨agermenge Y X (siehe 2.7) kann wie folgt implementiert werden: 221 dAutBeh :: DAut x y ([x] -> y) dAutBeh = DAut (\f x w -> f $ x:w) (\f -> f []) Fu¨r alle DAut(X, Y )-Algebren A implementieren folgende Funktionen die Entfaltungen von Elementen von A zu Verhaltensfunktionen bzw. DAut(X, Y )-Cotermen (siehe 2.9 bzw. Kapitel 20): unfoldDAutBeh :: DAut x y state -> state -> [x] -> y unfoldDAutBeh alg s [] = beta alg s unfoldDAutBeh alg s (x:w) = unfoldDAutBeh alg (delta alg s x) w unfoldDAut :: DAut x y state -> state -> DAutCot x y unfoldDAut alg s = State (unfoldDAut alg . delta alg s) (beta alg s) Nach Beispiel 10.4 realisieren sowohl eo als auch die Unteralgebra {esum, osum} von coTDAut(Z,2) die Verhaltensfunktionen even◦sum und odd◦sum, d.h. es gelten die folgenden Gleichungen: unfoldDAutBeh eo Esum = unfoldDAutBeh dAutCot esum = even . sum unfoldDAutBeh eo Osum = unfoldDAutBeh dAutCot osum = odd . sum o 222 Beispiel 11.5 Datentyp der JavaLight-Algebren (siehe Beispiel 4.2 und Java.hs) data JavaLight commands command sum_ sumsect prod prodsect factor disjunct conjunct literal = JavaLight {seq_ :: command -> commands -> commands, embed :: command -> commands, block :: commands -> command, assign :: String -> sum_ -> command, cond :: disjunct -> command -> command -> command, cond1,loop :: disjunct -> command -> command, sum_ :: prod -> sumsect -> sum_, sumsect :: String -> prod -> sumsect -> sumsect, nilS :: sumsect, prod :: factor -> prodsect -> prod, prodsect :: String -> factor -> prodsect -> prodsect, nilP :: prodsect, embedI :: Int -> factor, var :: String -> factor, encloseS :: sum_ -> factor, disjunct :: conjunct -> disjunct -> disjunct, embedC :: conjunct -> disjunct, conjunct :: literal -> conjunct -> conjunct, embedL :: literal -> conjunct, not_ :: literal -> literal, atom :: sum_ -> String -> sum_ -> literal, 223 embedB encloseD :: Bool -> literal, :: disjunct -> literal} 11.6 Die Termalgebra von JavaLight Zuna¨chst wird die Menge der JavaLight-Terme analog zu den List(X)- bzw. Reg(CS)Termen von 10.1 bzw. 10.2 durch mehrere Datentypen implementiert und zwar jeweils einen fu¨r jede Sorte von JavaLight: data Commands data Command data data data data data data data data Sum Sumsect Prod Prodsect Factor Disjunct Conjunct Literal = Seq (Command,Commands) | Embed Command deriving Show = Block Commands | Assign (String,Sum) | Cond (Disjunct,Command,Command) | Cond1 (Disjunct,Command) | Loop (Disjunct,Command) deriving Show = Sum (Prod,Sumsect) deriving Show = Sumsect (String,Prod,Sumsect) | NilS deriving Show = Prod (Factor,Prodsect) deriving Show = Prodsect (String,Factor,Prodsect) | NilP deriving Show = EmbedI Int | Var String | EncloseS Sum deriving Show = Disjunct (Conjunct,Disjunct) | EmbedC Conjunct deriving Show = Conjunct (Literal,Conjunct) | EmbedL Literal deriving Show = Not Literal | Atom (Sum,String,Sum) | EmbedB Bool | EncloseD Disjunct deriving Show Damit kann die JavaLight-Algebra TΣ(JavaLight) wie folgt implementiert werden: 224 javaTerm :: JavaLight Commands Command Sum Sumsect Prod Prodsect Factor Disjunct Conjunct Literal javaTerm = JavaLight (curry Seq) Embed Block (curry Assign) (curry3 Cond) (curry Cond1) (curry Loop) (curry Sum) (curry3 Sumsect) NilS (curry Prod) (curry3 Prodsect) NilP EmbedI Var EncloseS (curry Disjunct) EmbedC (curry Conjunct) EmbedL Not (curry3 Atom) EmbedB EncloseD 11.7 Die Wortalgebra von JavaLight javaWord :: JavaLight String String String String String String String String String String javaWord = JavaLight {seq_ = (++), embed = id, block = \cs -> " {"++cs++"}", assign = \x e -> x++" = "++e++"; ", cond = \e c c' -> "if "++e++c++" else"++c', cond1 = \e c -> "if " ++e++c, loop = \e c -> "while "++e++c, sum_ = (++), sumsect = \op e f -> op++e++f, nilS = "", prod = (++), prodsect = \op e f -> op++e++f, nilP = "", embedI = show, 225 var encloseS disjunct embedC conjunct embedL not_ atom embedB encloseD = = = = = = = = = = id, \e -> '(':e++")", \e e' -> e++" || "++e', id, \e e' -> e++" && "++e', id, \be -> '!':be, \e rel e' -> e++rel++e', show, \e -> '(':e++")"} 11.8 Das Zustandsmodell von JavaLight (siehe Beispiel 4.9) type St a = Store -> a type FInt = Int -> Int rel :: String -> Int -> Int -> Bool rel str = case str of "<" -> (<) ">" -> (>) "<=" -> (<=) ">=" -> (>=) "==" -> (==) "!=" -> (/=) 226 javaState :: JavaLight (St Store) (St Store) (St Int) (St FInt) (St Int) (St FInt) (St Int) (St Bool) (St Bool) (St Bool) javaState = JavaLight {seq_ = flip (.), embed = id, block = id, assign = \x e st -> update st x $ e st, cond = cond, cond1 = \f g -> cond f g id, loop = loop, sum_ = \f g st -> g st $ f st, sumsect = \str f g st i -> g st $ op str i $ f st, nilS = const id, prod = \f g st -> g st $ f st, prodsect = \str f g st i -> g st $ op str i $ f st, nilP = const id, embedI = const, var = flip ($), encloseS = id, disjunct = lift (||), embedC = id, conjunct = lift (&&), embedL = id, not_ = (not .), atom = \f str -> lift (rel str) f, embedB = const, 227 encloseD = id} where cond :: St Bool -> St Store -> St Store -> St Store cond f g h st = if f st then g st else h st loop :: St Bool -> St Store -> St Store loop f g = cond f (loop f g . g) id op str = case str of "+" -> (+) "-" -> (-) "*" -> (*) "/" -> div Z.B. hat das JavaLight-Programm prog = fact = 1; while x > 1 {fact = fact*x; x = x-1;} die folgende Interpretation in A = javaState: compileA JavaLight (prog) : Store → Store store 7→ λstr.if str = x then 0 else if str = fact then store(x)! else store(str) Ausdru¨cke werden hier so interpretiert wie im zweiten in Beispiel 4.9 definierten Modell von JavaLight. 228 Da die Ausfu¨hrung von Kommandos, zumindest von denen, die loop enthalten, manchmal nicht terminieren, mu¨ssen sie als partielle Funktionen interpretiert werden, was die Erweiterung der Tra¨germengen des Zustandsmodells zu ω-CPOs (halbgeordneten Mengen mit Kettensuprema und kleinstem Element) erfordert (siehe Kapitel 19). Tats¨achlich implementiert die Haskell-Funktion loop von javaState das zu einem ω-CPO erweiterte Zustandsmodell, dessen Details in Abschnitt 19.1 zu finden sind. o 11.9 Die Ableitungsbaumalgebra von JavaLight type TS = Tree String javaDeri :: JavaLight TS TS TS TS TS TS TS TS TS TS javaDeri = JavaLight {seq_ = \c c' -> F "Commands" [c,c'], embed = \c -> F "Commands" [c], block = \c -> command [c], assign = \x e -> command [leaf x,leaf "=",e,leaf ";"], cond = \e c c' -> command [leaf "if",e,c,leaf "else",c'], cond1 = \e c -> command [leaf "if",e,c], loop = \e c -> command [leaf "while",e,c], sum_ = \e f -> F "Sum" [e,f], sumsect = \op e f -> F "Sumsect" [leaf op,e,f], nilS = leaf "Sumsect", prod = \e f -> F "Prod" [e,f], 229 prodsect = \op e f -> F "Prodsect" [leaf op,e,f], nilP = leaf "Prodsect", embedI = \i -> factor [leaf $ show i], var = \x -> factor [leaf x], encloseS = \e -> factor [leaf "(",e,leaf ")"], disjunct = \e e'-> F "Disjunct" [e,leaf "||",e'], embedC = \e -> F "Disjunct" [e], conjunct = \e e'-> F "Conjunct" [e,leaf "&&",e'], embedL = \e -> F "Conjunct" [e], not_ = \be -> literal [leaf "!",be], atom = \e rel e' -> literal [e,leaf rel,e'], embedB = \b -> literal [leaf $ show b], encloseD = \e -> literal [leaf "(",e,leaf ")"]} where command = F "Command" factor = F "Factor" literal = F "Literal" leaf = flip F [] 230 11.10 Datentyp der XMLstore-Algebren (siehe Beispiel 4.3 und Compiler.hs) data XMLstore store orders person emails email items stock suppliers id = XMLstore {store :: stock -> store, storeO :: orders -> stock -> store, orders :: person -> items -> orders -> orders, embedO :: person -> items -> orders, person :: String -> person, personE :: String -> emails -> person, emails :: email -> emails -> emails, none :: emails, email :: String -> email, items :: id -> String -> items -> items, embedI :: id -> String -> items, stock :: id -> Int -> suppliers -> stock -> stock, embedS :: id -> Int -> suppliers -> stock, supplier :: person -> suppliers, parts :: stock -> suppliers, id_ :: String -> id} 231 ¨ 12 Attributierte Ubersetzung ¨ Um die Operationen der Zielalgebra einer Ubersetzung induktiv definieren zu k¨onnen, mu¨ssen Tr¨agermengen oft parametrisiert werden oder die Wertebereiche bereits parametrisierter Tra¨germengen um zusa¨tzliche Komponenten erweitert werden, die zur Berechnung ¨ von Parametern rekursiver Aufrufe des Ubersetzers ben¨otigt werden. Die Zielalgebra A hat dann die Form Av1 × . . . × Avm → Aa1 × . . . × Aan . (1) Die Indizes v1, . . . , vm und a1, . . . , an heißen vererbte Attribute (inherited attributes) bzw. abgeleitete Attribute (derived, synthesized attributes) von s. Fu¨r alle 1 ≤ i ≤ m und 1 ≤ j ≤ n ist Avi bzw. Aaj die Menge der m¨oglichen Werte des Attributs vi bzw. aj . Vererbte Attribut(wert)e sind z.B. Positionen, Adressen, Schachtelungstiefen, etc. Abgeleitete Attribut(wert)e sind das eigentliche Zielobjekt wie auch z.B. dessen Typ, Gr¨oße oder Platzbedarf, das selbst einen Wert eines abgeleiteten Attributs bildet. Gleichzeitig vererbte und abgeleitete Attribute heißen transient. Deren Werte bilden den Zustandsraum, der einen Compiler zur Transitionsfunktion eines Automaten macht, dessen Ein- und Ausgaben Quell- bzw. Zielprogramme sind. 232 Kurz gesagt, erga¨nzen Attribute einen Syntaxbaum um Zusatzinformation, die erforderlich ¨ ist, um ein bestimmtes Ubersetzungsproblem zu l¨osen. In unserem generischen Ansatz sind sie aber nicht – wie beim klassischen Begriff einer Attributgrammatik – Dekorationen von Syntaxb¨aumen, sondern Komponenten der jeweiligen Zielalgebra. ¨ Nach der Ubersetzung in eine Zielfunktion vom Typ (1) werden alle vererbten Attribute mit bestimmten Anfangswerten initialisiert. Dann werden die Zielfunktion auf die Anfangswerte angewendet und am Schluss das Ergebnistupel abgeleiteter Attributwerte mit π1 auf die erste Komponente, die das eigentliche Zielobjekt darstellt, projiziert. Die Funktion execute in den Diagrammen (1) und (9) von Kapitel 5 h¨angt vielfach auch von diesen Anfangswerten ab. Dann l¨asst sie sich in drei Teile zerlegen (siehe z.B. den Fall des JavaLight-Compilers in Abschnitt 16.6): Sei Ainh = Av1 × . . . × Avm und Ader = Aa1 × . . . × Aan . A = (Ainh → Ader ) initialize g Ainh × (Ainh → Ader ) execute (2) Mach f execute0 Ainh × Ader λ(v, f ).(v, f (v)) 233 Beispiel 12.1 Bin¨ ardarstellung rationaler Zahlen konkrete Syntax G abstrakte Syntax Σ(G) rat → nat. | rat0 | rat1 mkRat : nat → rat app0, app1 : rat → rat nat → 0 | 1 | nat0 | nat1 0, 1 : 1 → nat app0, app1 : nat → nat Die Zielalgebra A Arat enth¨alt neben dem eigentlichen Zielobjekt ein weiteres abgeleitetes Attribut mit Wertebereich Q, welches das Inkrement liefert, um das sich der Dezimalwert einer rationalen Zahl erh¨oht, wenn an die Mantisse ihrer Bin¨ardarstellung eine 1 angefu¨gt wird. Anat = N Arat = Q × Q 0A : Anat 1A : Anat 0A = 0 1A = 1 app0A : Anat → Anat app1A : Anat → Anat app0A(n) = n ∗ 2 app1A(n) = n ∗ 2 + 1 234 mkRatA : Anat → Arat mkRatA(val) = (val, 1) app0A : Arat → Arat app1A : Arat → Arat app0A(val, inc) = (val, inc/2) app1A(val, inc)) = (val + inc/2, inc/2) Beispiel 12.2 Strings mit Hoch- und Tiefstellungen konkrete Syntax G abstrakte Syntax Σ(G) string → string box | app : string × box → string string ↑ box | up : string × box → string string ↓ box | down : string × box → string box mkString : box → string box → (string) | Char mkBox : string → box embed : Char → box 235 Die Zielalgebra A Astring und Abox enthalten • ein vererbtes Attribut mit Wertebereich N2, das die Koordinaten der linken unteren Ecke des Rechtecks liefert, in das der eingelesene String geschrieben wird, • neben dem eigentlichen Zielobjekt ein weiteres abgeleitetes Attribut mit Wertebereich N3, das die L¨ange sowie - auf eine feste Grundlinie bezogen - H¨ohe und Tiefe des Rechtecks liefert. Astring = Abox = Strings mit Hoch- und Tiefstellungen × N2 → N3 appA : Astring × Abox → Astring appA(f, g)(x, y) = (str str0, l + l0, max h h0, max t t0) where (str, l, h, t) = f (x, y) (str0, l0, h0, t0) = g(x + l, y) upA : Astring × Abox → Astring 0 upA(f, g)(x, y) = (strstr , l + l0, h + h0 − 1, max t (t0 − h + 1)) where (str, l, h, t) = f (x, y) (str0, l0, h0, t0) = g(x + l, y + h − 1) 236 downA : Astring × Abox → Astring downA(f, g)(x, y) = (strstr0 , l + l0, max h (h0 − t − 1), t + t0 − 1) where (str, l, h, t) = f (x, y) (str0, l0, h0, t0) = g(x + l, y − t + 1) mkString A : Abox → Astring mkString A(f ) = f mkBoxA : Astring → Abox mkBoxA(f ) = f embedA : Char → Abox emdedA(c)(x, y) = (c, 1, 2, 0) ¨ Beispiel 12.3 Ubersetzung regul¨ arer Ausdru ¨ cke in erkennende Automaten (siehe 2.1) ¨ Die folgende Graphgrammatik beschreibt die Ubersetzung eines Reg(CS)-Terms t in einen nichtdeterministischen Automaten, der die Sprache fold Lang (t) von t erkennt (siehe [9], Abschnitt 3.2.3) 237 t 0 t 1 ε s' s eps s' s s mt s' s s – C s' s s' C s' t s par(t,t') s' s s' t' s seq(t,t') s' s t t' s' ε s iter(t) s' s ε t ε s' ε 238 Zuna¨chst wird Regel 1 auf t angewendet. Es entsteht ein Graph mit zwei Knoten und einer mit t markierten Kante. Dieser wird nun mit den anderen Regeln schrittweise verfeinert, bis an allen seinen Kanten nur noch Konstantenmengen von CS stehen. Dann stellt er den Transitionsgraphen eines Automaten dar, der t erkennt. 1 0 Mit obiger Graphgrammatik aus dem Reg(CS)-Term t = iter(par(seq(iter(a), a), seq(seq(iter(seq(b, c)), b), c))) konstruierter Automat zur Erkennung von fold Lang (t) 239 Wa¨hrend die obige Graphgrammatik von einer einzelnen Kante ausgeht und diese schrittweise zum erkennenden Automaten verfeinert, beginnt die Auswertung eines regul¨aren Ausdrucks in der folgenden Reg(CS)-Algebra regNDA mit der leeren Transitionsfunktion und fu¨gt schrittweise Transitionen hinzu und komponiert die Transitionsfunktion fu¨r die Anwendung eines regul¨aren Operators aus den Transitionsfunktionen fu¨r dessen Argumente. Die Zielalgebra regNDA Alle Zust¨ande seien ganze Zahlen. NDA = (N → ((1 + CS) → N∗)) ist der Typ (der Transitionsfunktionen) nichtdeterministischer Automaten mit ganzzahligen Zust¨anden. Jedes Element der Tr¨agermenge regNDAreg = (NDA × N3 → NDA × N) enth¨alt • den Automaten als transientes Attribut, das mit der Funktion λn.λs. (Automat ohne Zustandsu¨berg¨ange) initialisiert wird, • zwei vererbte Attribute mit Wertebereich N, die mit 0 bzw. 1 initialisiert werden und den Start- bzw. Endzustand des Automaten bezeichnen, • ein transientes Attribut mit Wertebereich N, das mit 2 initialisiert wird und die n¨achste ganze Zahl liefert, die als Zustandsname vergeben werden kann. 240 epsregNDA : regNDAreg epsregNDA(δ, s, s0, next) = (addTo(δ)(s)()(s0), next) mtregNDA : regNDAreg mtregNDA(δ, s, s0, next) = (δ, next) C : 1 → regNDAreg , C regNDA C ∈ CS (δ, s, s0, next) = (addTo(δ)(s)(C)(s0), next) parregNDA : regNDAreg × regNDAreg → regNDAreg parregNDA(f, g)(δ, s, s0, next) = g(δ 0, s, s0, next0) where (δ 0, next0) = f (δ, s, s0, next) seq regNDA : regNDAreg × regNDAreg → regNDAreg seq regNDA(f, g)(δ, s, s0, next) = g(δ 0, next, s0, next0) where (δ 0, next0) = f (δ, s, next, next + 1) 241 iterregNDA : regNDAreg → regNDAreg iterregNDA(f )(δ, s, s0, next) = (addTo(δ4)(s)()(s0), next3) where next1 = next + 1 next2 = next1 + 1 (δ1, next3) = f (δ, next, next1, next2) δ2 = addTo(δ1)(s)()(next) δ3 = addTo(δ2)(next1)()(next) δ4 = addTo(δ3)(next1)()(s0) x addTo(δ)(s)(x)(s0) fu¨gt die Transition s → s0 zur Transitionsfunktion δ hinzu. Haskell-Implementierung von addTo (siehe Kapitel 8): addTo :: (Eq a,Eq b,Eq c) => (a -> b -> [c]) -> a -> b -> c -> a -> b -> [c] addTo f a b c = update f a $ update (f a) b $ insert c $ f a b 242 Der durch Auswertung eines regul¨aren Ausdrucks in der Reg(CS)-Algebra regNDA erzeugte ¨ Automat NDA ist nichtdeterministisch und hat -Uberg ¨ange. Er l¨asst sich wie u¨blich in ¨ einen deterministischen Potenzautomaten ohne -Uberg ¨ange transformieren. Da 0 der Anfangszustand, 1 der Endzustand und auch alle anderen Zust¨ande von NDA natu¨rliche Zahlen sind, lassen sich alle Zust¨ande des Potenzautomaten als Listen natu¨rlicher Zahlen darstellen, wobei die -Hu¨lle von [0] sein Anfangszustand ist und eine Liste genau dann ein Endzustand ist, wenn sie 1 enth¨alt. Demnach kann ein Potenzautomat als Element des folgenden Datentyps aller Acc(String)Algebren implementiert werden: data Acc state = Acc {delta :: state -> String -> state, beta :: state -> Bool} (vgl. Beispiel 11.4). Man erha¨lt ihn im Fall CS = String ∗ aus der oben definierten Reg(CS)-Algebra regNDA wie folgt: type NDA = Int -> String -> [Int] type NDAStep = (NDA,Int,Int,Int) -> (NDA,Int) accND :: NDAStep -> Acc [Int] accND f = Acc delta (1 `elem`) where delta qs x = epsHull f $ deltaP f x qs 243 deltaP :: NDAStep -> String -> [Int] -> [Int] deltaP f x = unionMap $ flip (fst $ f (const2 [],0,1,2)) $ runPS int x contL $ const "Int" where contL _ = runPS bool x (const x) $ const "Bool" epsHull :: NDAStep -> [Int] -> [Int] epsHull f qs = if qs' `subset` qs then qs else epsHull f $ qs `union` qs' where qs' = deltaP f "eps" qs Die Funktion runPS (siehe Abschnitt 16.2) u¨bersetzt hier ganze Zahlen und Boolesche Werte mit Hilfe der Scanner int und bool in die Strings "Int" bzw. "Bool" und u¨bergibt diese anstelle der Werte als Eingabe an den Automaten. Andere Eingabesymbole werden durchgereicht. regNDA und accNDA sind im Haskell-Modul Compiler.hs implementiert. 12.4 Darstellung von Termen als hierarchische Listen Mit folgender JavaLight-Algebra javaList wird der Syntaxbaum des Programms fact = 1; while x > 1 {fact = fact*x; x = x-1;} in die Form einer hierarchischen Liste gebracht: 244 Die Tr¨agermengen von javaList enthalten zwei vererbte Attribute vom Typ 2 bzw. Z. Der Boolesche Wert gibt an, ob der jeweilige Teilstring str hinter oder unter den vorangehenden zu schreiben ist. Im zweiten Fall wird str um den Wert des zweiten Attributs eingeru¨ckt. 245 type BIS = Bool -> Int -> String javaList :: JavaLight BIS BIS BIS BIS BIS BIS BIS BIS BIS BIS javaList = JavaLight {seq_ = indent2 "commands", embed = indent1 "embed", block = indent1 "block", assign = \x e -> indent2 "assign" (indent0 x) e, cond = indent3 "cond", cond1 = indent2 "cond1", loop = indent2 "loop", sum_ = indent2 "sum", sumsect = \op e f -> indent3 "sumsect" (indent0 op) e f, nilS = indent0 "nilS", prod = indent2 "prod", prodsect = \op e f -> indent3 "prodsect" (indent0 op) e f, nilP = indent0 "nilP", embedI = \i -> indent1 "embedI" $ indent0 $ show i, var = \x -> indent1 "var" $ indent0 x, encloseS = indent1 "encloseS", disjunct = indent2 "disjunct", embedC = indent1 "embedC", conjunct = indent2 "conjunct", embedL = indent1 "embedL", not_ = indent1 "not", atom = \e rel e'-> indent3 "atom" e (indent0 rel) e', 246 embedB = \b -> indent1 "embedB" $ indent0 $ show b, encloseD = indent1 "encloseD"} where indent0 x = blanks x [] indent1 x f = blanks x [f] indent2 x f g = blanks x [f,g] indent3 x f g h = blanks x [f,g,h] blanks :: String -> [BIS] -> BIS blanks x fs b n = if b then str else '\n':replicate n ' '++str where str = case fs of f:fs -> x++' ':g True f++ concatMap (g False) fs _ -> x g b f = f b $ n+length x+1 12.5 Assemblersprache StackCom ∗ und JavaLight-Algebra javaStack Der folgende Datentyp liefert die Befehle einer Assemblersprache, die auf einem Keller vom Typ Z und einem Speicher vom Typ Store = String → Z operiert. Letzterer ist natu¨rlich nur die Abstraktion eines realen Speichers, auf dessen Inhalt nicht u¨ber Strings, sondern u¨ber Adressen, z.B. Kellerpositionen, zugegriffen wird. Ein solche – realistischere – Assemblersprache wird erst im na¨chsten Kapitel behandelt. 247 data StackCom = Push Int | Pop | Load String | Save String | Add | Sub | Mul | Div | Or_ | And_ | Inv | Cmp String | Jump Int | JumpF Int Diese Befehle bilden die mo¨glichen Eingaben eines endlichen Automaten, dessen Zusta¨nde jeweils aus einem Kellerinhalt und einer Variablenbelegung bestehen: type State = ([Int],Store,Int) Die Bedeutung der Befehle ergibt sich aus ihrer Verarbeitung durch eine Transitionsfunktion executeCom: executeCom :: StackCom -> State -> State executeCom com (stack,store,n) = case com of Push a -> (a:stack,store,n+1) Pop -> (tail stack,store,n+1) Load x -> (store x:stack,store,n+1) Save x -> (stack,update store x $ head stack,n+1) Add -> (a+b:s,store,n+1) where a:b:s = stack Sub -> (b-a:s,store,n+1) where a:b:s = stack Mul -> (a*b:s,store,n+1) where a:b:s = stack Div -> (b`div`a:s,store,n+1) where a:b:s = stack 248 Or_ And_ Inv Cmp str Jump k JumpF k -> -> -> -> (max a b:s,store,n+1) where a:b:s = stack (a*b:s,store,n+1) where a:b:s = stack ((a+1)`mod`2:s,store,n+1) where a:s = stack (c:s,store,n+1) where a:b:s = stack c = if rel str a b then 1 else 0 -- siehe 11.8 -> (stack,store,k) -> (stack,store,if a == 0 then k else n+1) where a:_ = stack Sprungbefehle k¨onnen nur im Kontext einer vollst¨andigen Befehlsfolge cs ausgefu¨hrt werden. n ist die Position des Befehls von cs, den der Interpreter als n¨achsten ausfu¨hrt: execute :: [StackCom] -> State -> State execute cs state@(_,_,n) = if n >= length cs then state else execute cs $ executeCom (cs!!n) state Um Sprungziele, also die ganzzahligen Parameter der Sprungbefehle, berechnen zu k¨onnen, muss der Compiler die Position jedes erzeugten Befehls innerhalb des gesamten Zielprogramms kennen. 249 Dieses erzeugt er durch Interpretation des (abstrakten) Quellprogramms in der folgenden JavaLight-Algebra javaStack , deren Tr¨agermengen neben dem Zielcode Werte zweier ganzzahliger Attribute enthalten, aus denen er die Sprungziele im Zielcode berechnet. Das erste Attribut ist vererbt und liefert die Nummer des ersten Befehls des jeweiligen Zielcodes, das zweite ist abgeleitet und liefert die L¨ange des Zielcodes. Dementsprechend interpretiert javaStack alle Sorten von JavaLight durch den Funktionstyp LCom = Int -> [StackCom] javaStack :: JavaLight LCom LCom LCom LCom LCom LCom LCom LCom LCom LCom javaStack = JavaLight {seq_ = seq_, embed = id, block = id, assign = \x e lab -> e lab++[Save x,Pop], cond = \e c c' lab -> let (code,exit) = fork e c 1 lab code' = c' exit in code++Jump (exit+length code'):code', cond1 = \e c lab -> fst $ fork e c 0 lab, loop = \e c lab -> fst (fork e c 1 lab)++[Jump lab], sum_ = apply2 "", sumsect = \op -> apply2 op . apply1 op, nilS = const [], prod = apply2 "", 250 prodsect nilP embedI var encloseS disjunct embedC conjunct embedL not_ atom embedB encloseD = = = = = = = = = = = = = \op -> apply2 op . apply1 op, const [], \i -> const [Push i], \x -> const [Load x], id, apply2 "|", id, apply2 "&", id, apply1 '!', \e rel -> apply2 rel e, \b -> const [Push $ if b then 1 else 0], id} where apply1 :: String -> LCom -> LCom apply1 op e lab = e lab++[case op of "!" -> Inv "+" -> Add; "*" -> Mul; "-" -> Sub "/" -> Div] seq_ :: LCom -> LCom -> LCom seq_ e e' lab = code++e' (lab+length code) where code = e lab 251 apply2 :: String -> LCom -> LCom -> LCom apply2 op e e' lab = code++e' (lab+length code)++ [case op of "|" -> Or_ "&" -> And_ _ -> Cmp op] where code = e lab fork :: LCom -> LCom -> Int -> Int -> ([StackCom],Int) fork e c n lab = (code++JumpF exit:code',exit) where code = e lab lab' = lab+length code code' = c lab' exit = lab'+1+length code'+n Beispiel 12.6 (Fakult¨atsfunktion) Steht das JavaLight-Programm fact = 1; while x > 1 {fact = fact*x; x = x-1;} in der Datei prog, dann u¨bersetzt es javaToAlg "prog" 6 (siehe Java.hs) in ein Zielprogramm vom Typ [StackCom] und schreibt dieses in die Datei javatarget ab. Es lautet wie folgt: 252 0: 1: 2: 3: 4: 5: 6: 7: Push 1 Save "fact" Pop Load "x" Push 1 Cmp ">" JumpF 18 Load "fact" 8: 9: 10: 11: 12: 13: 14: 15: Load Mul Save Pop Load Push Sub Save "x" 16: Pop 17: Jump 3 "fact" "x" 1 "x" 253 13 JavaLight+ = JavaLight + I/O + Deklarationen + Prozeduren (siehe Java2.hs) 13.1 Assemblersprache mit I/O und Kelleradressierung Die Variablenbelegung store : String → Z im Zustandsmodell von Abschnitt Assemblerprogramme als JavaLight-Zielalgebra wird ersetzt durch den Keller stack ∈ Z∗, der jetzt nicht nur der schrittweisen Auswertung von Ausdru¨cken dient, sondern auch der Ablage von Variablenwerten unter vom Compiler berechneten Adressen. Weitere Zustandskomponenten sind: • der Inhalt des Registers BA fu¨r die jeweils aktuelle Basisadresse (s.u.), • der Inhalt des Registers STP fu¨r die Basisadresse des statischen Vorg¨angers des jeweils zu u¨bersetzenden Blocks bzw. Funktionsaufrufs (s.u.), • der schon in Abschnitt 12.5 benutzte Befehlsz¨ ahler pc (program counter), • der Ein/Ausgabestrom io, auf den Lese- bzw. Schreibbefehle zugreifen. Der entsprechende Datentyp lautet daher wie folgt: data State = State {stack,io :: [Int], ba,stp,pc :: Int} 254 ¨ Bei der Ubersetzung eines Blocks b oder Prozeduraufrufs f (es) reserviert der Compiler Speicherplatz fu¨r die Werte aller lokalen Variablen des Blocks b bzw. Rumpfs (der Deklaration) von f . Dieser Speicherplatz ist Teil eines b bzw. f (es) zugeordneten Kellerabschnitts (stack frame, activation record). Dessen Anfangsadresse ist die Basisadresse ba der lokalen Variablen. Die absolute Adresse einer lokalen Variablen x ist die Kellerposition, an welcher der jeweils aktuelle Wert von x steht. Sie ist immer die Summe von ba und dem – Relativadresse von x genannten und vom Compiler in der Symboltabelle (s.u.) gespeicherten – Abstand zum Beginn des Kellerabschnitts. Wird zur Laufzeit der Block b bzw. – als Teil der Ausfu¨hrung von f (es) – der Rumpf von f betreten, dann speichert ein vom Compiler erzeugter Befehl die n¨achste freie Kellerposition als aktuelle Basisadresse im Register BA. Der statische Vorg¨ anger von b bzw. f (es) ist der innerste Block bzw. Prozedurrumpf, in dem b bzw. die Deklaration von f steht. Der b bzw. f (es) zugeordnete Kellerabschnitt beginnt stets mit dem Display, das aus den – nach aufsteigender Schachtelungstiefe geordneten – Basisadressen der Kellerabschnitte aller umfassenden Bl¨ocke und Prozedurru¨mpfe besteht. 255 Der Compiler erzeugt und verwendet keine ganzzahligen, sondern nur symbolische Adressen, d.h. Registernamen (BA, STP, TOP) oder mit einer ganzen Zahl indizierte (inDexed) Adressen der Form Dex(adr)(i): data SymAdr = BA | STP | TOP | Dex SymAdr Int | Con Int Der Inhalt von TOP ist immer die Adresse der ersten freien Kellerposition. Ist s der Kellerinhalt, dann entspricht die absolute Adresse adr der Kellerposition |s| − 1 − adr. 256 Die folgende Funktion baseAdr berechnet die jeweils aktuelle (symbolische) Basisadresse: baseAdr :: Int -> Int -> SymAdr baseAdr declDep dep = if declDep == dep then BA else Dex BA declDep ¨ Der Compiler ruft baseAdr bei der Ubersetzung jeder Verwendung einer Variable x auf. declDep und dep bezeichnen die Deklarations- bzw. Verwendungstiefe von x. Stimmen beide Werte u¨berein, dann ist x eine lokale Variable und die Basisadresse von x steht (zur Laufzeit) im Register BA. Andernfalls ist x eine globale Variable, d.h. x wurde in einem Block bzw. Prozedurrumpf deklariert, der denjenigen, in dem x verwendet wird, umfasst (declDep<dep). Die Basisadresse von x ist in diesem Fall nicht im Register BA, sondern unter dem Inhalt der declDep-ten Position des dem Block bzw. Prozedurrumpf, in dem x verwendet wird, zugeordneten Kellerabschnitt zu finden (s.o.). Die folgenden Funktionen berechnen aus symbolischen Adressen absolute Adressen bzw. Kellerinhalte: absAdr,contents :: State -> SymAdr -> Int absAdr _ (Con i) = i absAdr state BA = ba state absAdr state STP = stp state 257 absAdr state TOP absAdr state (Dex BA i) absAdr state (Dex STP i) absAdr state (Dex TOP i) absAdr state (Dex adr i) contents state (Dex adr i) contents state adr = = = = = = length $ stack state ba state+i stp state+i length (stack state)+i contents state adr+i s!!(k-i) where (s,k) = stackPos state adr = absAdr state adr stackPos :: State -> SymAdr -> ([Int],Int) stackPos state adr = (s,length s-1-contents state adr) where s = stack state updState updState updState updState :: State -> SymAdr -> state BA x = state STP x = state (Dex adr i) x = Int -> State state {ba = x} state {stp = x} state {stack = updList s (k-i) x} where (s,k) = stackPos state adr Mit der Anwendung von absAdr oder contents auf eine – evtl. geschachtelte – indizierte Adresse wird eine Folge von Kelleradressen und -inhalten durchlaufen. 258 absAdr liefert die letzte Adresse der Folge, contents den Kellerinhalt an der durch diese Adresse beschriebenen Position. Die Zielprogramme von Javalight+ setzen sich aus folgenden Assemblerbefehlen mit symbolischen Adressen zusammen: data StackCom = PushA SymAdr | Push SymAdr | Pop | Save SymAdr | Move SymAdr SymAdr | Add | Sub | Mul | Div | Or_ | And_ | Inv | Cmp String | Jump SymAdr | JumpF Int | Read SymAdr | Write Analog zu Abschnitt 12.5 ist die Bedeutung der Befehle durch eine Transitionsfunktion executeCom gegeben – die jetzt auch die Sprungbefehle verarbeitet: executeCom :: StackCom -> State -> State executeCom com state = case com of PushA adr -> state' {stack = absAdr state adr:stack state} Push adr -> state' {stack = contents state adr: stack state} Pop -> state' {stack = tail $ stack state} Save adr -> updState state' adr $ head $ stack state 259 Move adr adr' -> updState state' adr' $ contents state adr Add -> applyOp state' (+) Sub -> applyOp state' (-) Mul -> applyOp state' (*) Div -> applyOp state' div Or_ -> applyOp state' max And_ -> applyOp state' (*) Inv -> state' {stack = (a+1)`mod`2:s} where a:s = stack state Cmp rel -> state' {stack = mkInt (evalRel rel b a):s} where a:b:s = stack state Jump adr -> state {pc = contents state adr} JumpF lab -> state {pc = if a == 0 then lab else pc state+1, stack = s} where a:s = stack state Read adr -> if null s then state' else (updState state' adr $ head s) {io = tail s} where s = io state Write -> if null s then state' else state' {io = io state++[head s]} 260 where s = stack state where state' = state {pc = pc state+1} applyOp :: State -> (Int -> Int -> Int) -> State applyOp state op = state {stack = op b a:s} where a:b:s = stack state execute wird ebenfalls an das neue Zustandsmodell angepasst: execute :: [StackCom] -> State -> State execute cs state = if curr >= length cs then state else execute cs $ executeCom (cs!!curr) state where curr = pc state 13.2 Grammatik und abstrakte Syntax von JavaLight+ JavaLight+ enth¨alt neben den Sorten von JavaLight die Sorten Formals und Actuals fu¨r Listen formaler bzw. aktueller Parameter von Prozeduren. Auch die Konstantenmengen von JavaLight werden u¨bernommen. Hinzu kommt eine fu¨r formale Parameter. Sie besteht aus mit zwei Konstruktoren aus dem jeweiligen Parameternamen und einem Typdeskriptor gebildeten Ausdruck: 261 data TypeDesc = INT | BOOL | UNIT | Fun TypeDesc Int | ForFun TypeDesc data Formal = Par String TypeDesc | FunPar String [Formal] TypeDesc FunPar(x)(t) bezeichnet einen funktionalen formalen Parameter, also eine Prozedurvariable. Sie hat den Typ ForFun(t). t ist hier der Typ der Prozedurergebnisse. Demgegenu¨ber bezeichnet Fun(t,lab) den Typ einer Prozedurkonstanten mit Ergebnistyp t und Codeadresse lab. Dementsprechend enth¨alt JavaLight+ auch die Regeln von JavaLight. Hinzu kommen die folgenden Regeln fu¨r Ein/Ausgabebefehle, Deklarationen, Prozeduraufrufe und Parameterlisten. Außerdem sind jetzt auch Boolesche Variablen und Zuweisungen an diese zugelassen. Command → Formals Formals 0 ExpSemi ExpBrac ExpComm Factor → → → → → → read String; | write ExpSemi | {Commands} | TypeDesc String Formals {Commands} | TypeDesc String | String = ExpSemi | String Formals {Commands} | String Actuals () | (Formals 0 Formal ) | Formal , Formals 0 Sum; | Disjunct; Sum) | Disjunct) Sum, | Disjunct, String Actuals 262 Literal → Actuals → Actuals0 → String | String Actuals () | (Actuals0 ExpBrac | ExpComm Actuals0 In Beispiel 6.2 wurde begru¨ndet, warum drei verschiedene Sorten fu¨r Ausdru¨cke (ExpSemi , ¨ ExpBrac und ExpComm) beno¨tigt werden. Beim Ubergang zur abstrakten Syntax ko¨nnen wir die Unterscheidung zwischen den drei Sorten wieder aufheben. Erlaubt man nur JavaLight+-Algebren A, die Formals durch Formal ∗ und Actuals durch (ASum + ADisjunct )∗ interpretieren, dann lautet der Datentyp der JavaLight+-Algebren wie folgt (siehe Java2.hs): data JavaLightP commands command exp sum_ sumsect prod prodsect factor disjunct conjunct literal formals actuals = JavaLightP {seq_ :: command -> commands -> commands, embed :: command -> commands, block :: commands -> command, assign :: String -> exp -> command, applyProc :: String -> actuals -> command, cond :: disjunct -> command -> command -> command, cond1,loop :: disjunct -> command -> command, read_ :: String -> command, write_ :: exp -> command, vardecl :: String -> TypeDesc -> command, 263 fundecl formals embedS sum_ sumsect nilS prod prodsect nilP embedI varInt applyInt encloseS embedD disjunct embedC conjunct embedL not_ atom embedB varBool applyBool encloseD actuals :: :: :: :: :: :: :: :: :: :: :: :: :: :: :: :: :: :: :: :: :: :: :: :: :: String -> formals -> TypeDesc -> commands -> command, [Formal] -> formals, sum_ -> exp, prod -> sumsect -> sum_, String -> prod -> sumsect -> sumsect, sumsect, factor -> prodsect -> prod, String -> factor -> prodsect -> prodsect, prodsect, Int -> factor, String -> factor, String -> actuals -> factor, sum_ -> factor, disjunct -> exp, conjunct -> disjunct -> disjunct, conjunct -> disjunct, literal -> conjunct -> conjunct, literal -> conjunct, literal -> literal, sum_ -> String -> sum_ -> literal, Bool -> literal, String -> literal, String -> actuals -> literal, disjunct -> literal, [exp] -> actuals} 264 13.3 JavaLight+-Algebra javaStackP (siehe Java2.hs) javaStackP hat wie javaStack nur eine Tr¨agermenge (ComStack) fu¨r alle Kommandosorten sowie eine Tr¨agermenge (ExpStack) fu¨r alle Ausdruckssorten (außer denen fu¨r Sektionen): type ComStack = Int -> Symtab -> Int -> Int -> ([StackCom],Symtab,Int) type ExpStack = Int -> Symtab -> Int -> ([StackCom],TypeDesc) type Symtab = String -> (TypeDesc,Int,Int) Die Symboltabelle (vom Typ Symtab) ordnet jeder Variablen drei Werte zu: Typ, Schachtelungstiefe ihrer Deklaration, also die Zahl der die Deklaration umfassenden Bl¨ocke und Prozedurru¨mpfe, sowie eine Relativadresse (s.o.). Transiente Attribute der Kommandosorten sind die Symboltabelle und die n¨achste freie Relativadresse (adr; s.u.). Weitere vererbte Attribute der Kommando- und Ausdruckssorten sind die n¨achste freie Befehlsnummer (lab; s.u.) und die Schachtelungstiefe des jeweiligen Kommandos bzw. Ausdrucks depth; s.u.). Weitere abgeleitete Attribute der Kommando- und Ausdruckssorten sind der Zielcode und seine L¨ange. Die Ausdruckssorten haben außerdem die Symboltabelle als vererbtes und den Typdeskriptor des jeweiligen Ausdrucks als abgeleitetes Attribut. 265 Listen formaler bzw. aktueller Parameter werden von javaStackP als Elemente der folgenden Tr¨agermengen interpretiert: type FormsStack = Symtab -> Int -> Int -> ([ComStack],Symtab,Int) type ActsStack = Int -> Symtab -> Int -> ([StackCom],Int) Formale Parameter sind Teile von Funktionsdeklarationen. Deshalb haben Listen formaler Parameter Attribute von Kommandosorten: Symboltabelle, Schachtelungstiefe n¨achste freie Relativadresse und sogar zus¨atzlichen Quellcode (vom Typ [ComStack]), der aus je¨ dem funktionalen Parameter eine lokale Funktionsdeklaration erzeugt (siehe Ubersetzung formaler Parameter). Aktuelle Parameter sind Ausdru¨cke und haben deshalb (fast) die gleichen Attribute wie Ausdruckssorten. Anstelle eines Typdeskriptors wird die L¨ange der jeweiligen Parameter berechnet. Den Platz von TypeDesc nimmt daher Int ein. Aktuelle Parameter k¨onnen im Gegensatz zu anderen Vorkommen von Ausdru¨cken Prozedurvariablen sein. Der Einfachheit halber u¨berpru¨ft unser Compiler nicht, ob diese nur an Parameterpositionen auftreten, so wie er auch Anwendungen arithmetischer Operationen auf Boolesche Variablen oder Boolescher Operationen auf Variablen vom Typ Z ignoriert. 266 Die Typvertra¨glichkeit von Deklarationen mit den Verwendungen der deklarierten Variablen k¨onnte aber mit Hilfe einer Variante von javaStackP u¨berpru¨ft werden, deren Tr¨agermengen um Fehlermeldungen angereichert sind. Bis auf die Interpretation von Bl¨ocken und die Einbindung der o.g. Attribute gleicht die Interpretation der Programmkonstrukte von JavaLight in javaStackP derjenigen in javaStack . An die Stelle der Lade- und Speicherbefehle, die javaStack erzeugt und die zur Laufzeit Daten von einer Variablenbelegung store : String → Z zum Keller bzw. vom Keller nach store transportieren, treten die oben definierten Lade- und Speicherbefehle, die Daten oder Kelleradressen zwischen Kellerpl¨atzen hin- und herschieben. javaStackP :: JavaAlgP ComStack ComStack ExpStack ExpStack ExpStack ExpStack ExpStack ExpStack ExpStack ExpStack ExpStack FormsStack ActsStack javaStackP = JavaLightP {seq_ = seq_, embed = id, block = block, assign = assign, applyProc = applyProc, cond = cond, cond1 = \e c -> (((fst .) .) .) . fork e c 0 loop = loop, read_ = read_, write_ = write_, 267 vardecl fundecl formals embedS sum_ sumsect nilS prod prodsect nilP embedI varInt applyInt encloseS embedD disjunct embedC conjunct embedL not_ atom embedB varBool applyBool encloseD = = = = = = = = = = = = = = = = = = = = = = = = = vardecl, fundecl, formals, id, apply2 "", \op -> apply2 "" . apply1 op, _ _ -> ([],INT), apply2 "", \op -> apply2 "" . apply1 op, \_ _ _ -> ([],INT), \i _ _ _ -> ([Push $ Con i],INT), var, applyFun, id, id, apply2 "|", id, apply2 "&", id, apply1 "!", \e rel e' -> apply (Cmp rel) (e,e'), \b _ _ _ -> ([Push $ Con $ mkInt b],BOOL), var, applyFun, id, 268 actuals = actuals} where apply1 :: String -> ExpStack -> ExpStack apply1 op e lab st dep = (fst (e lab st dep)++ [case op of "!" -> Inv; "+" -> Add; "-" -> Sub "*" -> Mul; _ -> Div], INT) seq_ :: ComStack -> ComStack -> ComStack seq_ c c' lab st dep adr = (code++code',st2,adr2) where (code,st1,adr1) = c lab st dep adr (code',st2,adr2) = c' (lab+length code) st1 dep adr1 apply2 :: String -> ExpStack -> ExpStack -> ExpStack apply2 op e e' lab st dep = (code++code'++cs,td) where (code,td) = e lab st dep code' = fst $ e' (lab+length code) st dep cs = case op of "" -> []; "|" -> [Or_] "&" -> [And_]; _ -> [Cmp op] ¨ Ubersetzung eines Blocks block :: ComStack -> ComStack block c lab st dep adr = (code',st,adr) where bodylab = lab+dep+3; dep' = dep+1 (code,_,local) = c bodylab st dep' dep' 269 bodylab: code' = Move TOP STP:pushDisplay BA dep++Move STP BA: code++replicate (local-dep') Pop++Save BA: replicate dep' Pop pushDisplay :: Int -> SymAdr -> [StackCom] pushDisplay dep reg = foldr push [Push reg] [0..dep-1] where push i code = Push (Dex reg i):code javaStackP umschließt im Gegensatz zu javaStack bei der Zusammenfassung einer Kommandofolge cs zu einem Block den Code von cs mit zus¨atzlichen Zielcode: Move TOP STP Speichern des Stacktops im Register STP pushDisplay dep BA Kellern des Displays des umfassenden Blocks und dessen Adresse Move STP BA Der Inhalt von STP wird zur neuen Basisadresse code Zielcode fu ¨r die Kommandofolge des Blocks replicate (local-dep') Pop Entkellern der lokalen Variablen des Blocks Save BA Speichern der alten Basisadresse in BA replicate dep' Pop Entkellern des Displays ¨ Ubersetzung einer Zuweisung assign :: String -> ExpStack -> ComStack assign x e lab st dep adr = (fst (e lab st dep)++[Save $ Dex ba adrx,Pop], st,adr) where (_,declDep,adrx) = st x ba = baseAdr declDep dep 270 Zielcodeerl¨auterung: Zielcode fu ¨r den Ausdruck e, der mit einem Befehl zur Kellerung des aktuellen Wertes von e schließt Save $ Dex ba adrx Speichern des aktuellen Wertes von e unter der Kelleradresse Dex ba adrx von x, die sich aus der Basisadresse ba von x und der in der Symboltabelle gespeicherten Relativadresse adrx von x ergibt Pop Entkellern des aktuellen Wertes von e code ¨ Ubersetzung von Konditionalen und Schleifen cond :: ExpStack -> ComStack -> ComStack -> ComStack cond e c c' lab st dep adr = (code++Jump (Con $ exit+length code'):code', st2,adr2) where ((code,st1,adr1),exit) = fork e c 1 lab st dep adr (code',st2,adr2) = c' exit st1 dep adr1 loop :: ExpStack -> ComStack -> ComStack loop e c lab st dep adr = (code++[Jump $ Con lab],st',adr') where (code,st',adr') = fst $ fork e c 1 lab st dep adr fork :: ExpStack -> ComStack -> Int -> Int -> Symtab -> Int -> Int -> (([StackCom],Int,Symtab,Int),Int) fork e c n lab st dep adr = ((code++JumpF exit:code',st',adr'),exit) where code = fst $ e lab st dep lab' = lab+length code+1 271 (code',st',adr') = c lab' st dep adr exit = lab'+length code'+n ¨ Ubersetzung eines Lesebefehls read_ :: String -> ComStack read_ x _ st dep adr = ([Read $ Dex ba adrx],st,adr) where (_,declDep,adrx) = st x ba = baseAdr declDep dep Zielcodeerl¨auterung: Read $ Dex ba adrx Speichern des ersten Elementes c des Ein/Ausgabestroms io unter der Adresse Dex ba adrx, die sich aus der Basisadresse ba des Kellerbereichs, in dem x deklariert wurde, und der in der Symboltabelle gespeicherten Relativadresse adrx von x ergibt. c wird aus io entfernt. ¨ Ubersetzung eines Schreibbefehls write_ :: ExpStack -> ComStack write_ e lab st dep adr = (fst (e lab st dep)++[Write,Pop],st,adr) Zielcodeerl¨auterung: Zielcode fu ¨r den Ausdruck e, der mit einem Befehl zur Kellerung des aktuellen Wertes von e schließt Write Anh¨angen des Wertes von e an den Ein/Ausgabestrom io code 272 Pop Entkellern des aktuellen Wertes von e ¨ Ubersetzung der Deklaration einer nichtfunktionalen Variable vardecl :: String -> TypeDesc -> ComStack vardecl x td _ st dep adr = ([Push $ Con 0],update st x (td,dep,adr),adr+1) Reservierung eines Kellerplatzes fu ¨r Werte von x Außerdem tr¨agt vardecl den zum Typ von x geh¨origen Typdeskriptor sowie dep (aktuelle Schachtelungstiefe) und adr (n¨achste freie Relativadresse) unter x in die Symboltabelle ein. ¨ Ubersetzung einer Funktions- oder Prozedurdeklaration fundecl :: String -> FormsStack -> TypeDesc -> ComStack -> ComStack fundecl f pars td body lab st dep adr = (code',st1,adr+1) where codelab = lab+2 st1 = update st f (Fun td codelab,dep,adr) dep' = dep+1 (parcode,st2,_) = pars st1 dep' $ -2 coms = foldl1 seq_ $ parcode++[body] bodylab = codelab+dep+2 (code,_,local) = coms bodylab st2 dep' dep' retlab = Dex TOP $ -1 exit = bodylab+length code+local+1 code' = Push (Con 0):Jump (Con exit): codelab: Move TOP BA:pushDisplay dep STP++ bodylab: code++replicate local Pop++[Jump retlab] 273 exit: Zielcodeerl¨auterung: Push $ Con 0 Jump $ Con exit Reservierung eines Kellerplatzes fu ¨r den Wert eines Aufrufs von f Sprung hinter den Zielcode Der Code zwischen codelab und exit wird erst bei der Ausfu ¨hrung des Zielcodes eines Aufrufs von f abgearbeitet (siehe applyFun): Die n¨achste freie Kellerposition wird zur neuen Basisadresse. Dieser Befehl hat die von fundecl berechnete Nummer codelab und wird angesprungen, wenn der Befehl Jump codelab des Zielcodes eines Aufrufs von f ausgefu ¨hrt wird (siehe applyProc). pushDisplay dep STP Kellern des Displays des umfassenden Prozedurrumpfs und dessen Adresse parcode++[body] erweiterter Prozedurrumpf (siehe formals) replicate local Pop Entkellern der lokalen Variablen und des Displays des Aufrufs von f Jump retlab Sprung zur Ru ¨cksprungadresse, die bei der Ausfu ¨hrung des Zielcodes des Aufruf von f vor dem Sprung zur Adresse des Codes von f gekellert wurde, so dass sie am Ende von dessen Ausfu ¨hrung wieder oben im Keller steht Move TOP BA ¨ Ubersetzung formaler Parameter formals :: [Formal] -> FormsStack formals pars st dep adr = foldl f ([],st,adr) pars where f (cs,st,adr) (Par x td) = (cs,update st x (td,dep,adr'),adr') (1) 274 where adr' = adr-1 f (cs,st,adr) (FunPar x@('@':g) pars td) = (cs++[c],st',adr') (2) where adr' = adr-3 st' = update st x (ForFun td,dep,adr') c = fundecl g (formals pars) td $ assign g $ applyFun x $ actuals $ map act pars act (Par x _) = var x act (FunPar (_:g) _ _) = var g Formale Parameter einer Prozedur g sind Variablen mit negativen Relativadressen, weil ihre aktuellen Werte beim Aufruf von g direkt vor dem fu ¨r den Aufruf reservierten Kellerabschnitt abgelegt werden. Eine Variable vom Typ INT (Fall 1) ben¨otigt einen Kellerplatz fu ¨r ihre aktuelle Basisadresse, eine Prozedurvariable (Fall 2) hingegen drei: • einen fu ¨r die aktuelle Basisadresse, die hier die Anfangsadresse des dem aktuellen Prozeduraufruf zugeordneten Kellerabschnitt ist, • einen fu ¨r die aktuelle Codeadresse und • einen fu ¨r die aktuelle Resultatadresse, das ist die Position des Kellerplatzes mit dem Wert des aktuellen Prozeduraufrufs. Der Compiler formal (siehe Java2.hs) erkennt einen formalen Funktions- oder Prozedurparameter g an dessen Parameterliste pars und speichert diesen unter dem Namen @g. formals erzeugt den attributierten Code c der Funktionsdeklaration g(pars){g = @g(pars)} und u ¨bergibt ihn als Teil von parcode an die Deklaration des statischen Vorg¨angers von g (s.u.). Damit werden g feste Basis-, Code- und Resultatadressen zugeordnet, so dass nicht nur die Aufrufe 275 von g, sondern auch die Zugriffe auf g als Variable korrekt u ¨bersetzt werden k¨onnen. ¨ Ubersetzung von Variablenzugriffen var :: String -> ExpStack var x _ st dep = case td of Fun _ codelab -> ([Push ba,Push $ Con codelab, PushA $ Dex ba adr],td) _ -> ([Push $ Dex ba adr],td) where (td,declDep,adr) = st x ba = baseAdr declDep dep (1) (2) Zielcodeerla¨uterung: Im Fall (1) ist x ein aktueller Parameter eines Aufrufs e = g(e1 , . . . , en ) einer Prozedur g, d.h. es gibt 1 ≤ i ≤ n mit ei = x, und x ist selbst der Name einer Prozedur! Im Quellprogramm geht e eine Deklaration von g voran, deren i-ter formaler Parameter eine Prozedurvariable f ist. f wird bei der Ausfu ¨hrung des Codes fu ¨r e durch x aktualisiert, indem die drei von (siehe formals) fu ¨r f reservierten Kellerpl¨atze mit den o.g. drei Wertkomponenten von x belegt werden (siehe parcode in applyFun). Beim Aufruf von x werden die drei Komponenten gekellert: Push ba Push $ Con codelab PushA $ Dex ba adr Kellern der Basisadresse ba von x Kellern der Codeadresse codelab von x Kellern der Resultatadresse Dex ba adr von x, die sich aus der Basisadresse ba von x und der von fundecl bzw. formals in die Symboltabelle eingetragenen Relativadresse adr fu ¨r das Resultat von e ergibt 276 Im Fall (2) genu ¨gt ein Befehl: Push $ Dex ba adr Kellern des Wertes von x, der unter der Kelleradresse Dex ba adr steht, die sich aus der Basisadresse ba von x und der in der von vardecl bzw. formals in die Symboltabelle eingetragenen Relativadresse adr von x ergibt ¨ Ubersetzung von Prozeduraufrufen applyFun :: String -> ActsStack -> ExpStack applyFun f acts lab st dep = case td of Fun td codelab -> (code ba (Con codelab) $ Dex ba adr, (1) td) ForFun td -> (code (Dex ba adr) (Dex ba $ adr+1) (2) $ Dex (Dex ba $ adr+2) 0, td) where (td,declDep,adr) = st f (parcode,parLg) = acts lab st dep retlab = lab+length parcode+4 ba = baseAdr declDep dep code ba codelab result = parcode++Push BA:Move ba STP: Push (Con retlab):Jump codelab: retlab: Pop:Save BA:Pop:replicate parLg Pop++ [Push result] 277 Zielcodeerl¨auterung: parcode Push BA Move ba STP Push $ Con retlab Jump codelab Zielcode fu ¨r die Aufrufparameter Kellern der aktuellen Basisadresse Speichern der Basisadresse von f im Register STP Kellern der Ru ¨cksprungadresse retlab Sprung zur Codeadresse codelab von f, die von fundecl berechnet wurde An dieser Stelle wird bei der Ausfu ¨hrung des Zielcodes zun¨achst code(f ) abgearbeitet (siehe fundecl). Dann geht es weiter mit: Pop Save BA Pop replicate parLg Pop Push result Entkellern der Ru ¨cksprungadresse retlab Speichern der alten Basisadresse in BA Entkellern der alten Basisadresse Entkellern der Aufrufparameter Kellern des Aufrufwertes ¨ Im Fall (1) geht dem Aufruf von f im Quellprogramm eine Deklaration von f voran, deren Ubersetzung die Symboltabelle um Eintr¨age fu ¨r f erweitert hat, aus denen applyFun die Adressen bzw. Befehlsnummern ba, codelab und result berechnet und in obigen Zielcode einsetzt. 278 Im Fall (2) ist f eine Prozedurvariable, d.h. im Quellprogramm kommt der Aufruf von f im Rumpf der Deklaration einer Prozedur g vor, die f als formalen Parameter enth¨alt. Der Zielcode wird erst bei einem Aufruf von g ausgefu ¨hrt, d.h. nachdem f durch eine Prozedur h aktualisiert und die von formals fu ¨r f reservierten Kellerpl¨atze belegt wurden. Der Zielcode des Aufrufs von f ist also in Wirklichkeit Zielcode fu ¨r einen Aufruf von h. Dementsprechend sind ba die Anfangsadresse des dem Aufruf zugeordneten Kellerabschnitts und damit Dex ba adr, Dex ba $ adr+1 und Dex ba $ adr+2 die Positionen der Kellerpl¨atze mit der Basisadresse, der Codeadresse bzw. der Resultatadresse von h. Folglich kommt applyFun durch Zugriff auf diese Pl¨atze an die aktuellen Werte von ba, codelab und result heran und kann sie wie im Fall (1) in den Zielcode des Aufrufs einsetzen. Damit Push result analog zum Fall (1) nicht die Resultatadresse von h, sondern den dort abgelegten Wert kellert, muss sie vorher derefenziert werden. Deshalb wird result im Fall (2) auf Dex (Dex ba $ adr+2) 0 gesetzt. 279 Display d. statischen Vorgängers Basisadr. d. stat. Vorgängers STP Display d. statischen Vorgängers Display d. statischen Vorgängers BA Parameter Parameter Parameter Basisadr. d. dyn. Vorgängers Basisadr. d. dyn. Vorgängers Basisadr. d. dyn. Vorgängers Rücksprungadresse Rücksprungadresse Rücksprungadresse TOP TOP BA BA Display d. statischen Vorgängers Basisadr. d. stat. Vorgängers lokale Adressen TOP Kellerzustand beim Sprung zur Codeadresse Kellerzustand während der Ausführung des Funktionsrumpfes Kellerzustand beim Rücksprung Der Zielcode der Parameterliste acts setzt sich aus dem Zielcode der einzelnen Ausdru ¨cke von acts zusammen, wobei acts von hinten nach vorn abgearbeitet wird, weil in dieser Reihenfolge Speicherplatz fu ¨r die entsprechenden formalen Parameter reserviert wurde (siehe formals). 280 Den Aufruf einer Prozedur p ohne Ru ¨ckgabewert (genauer gesagt: mit Ru ¨ckgabewert vom Typ UNIT) betten wir in eine Zuweisung an p ein: applyProc :: String -> ActsStack -> ComStack applyProc f p = assign p . applyFun p ¨ Ubersetzung aktueller Parameter actuals :: [ExpStack] -> ActsStack actuals pars lab st dep = foldr f ([],0) pars where f e (code,parLg) = (code++code',parLg+ case td of Fun _ _ -> 3 _ -> 1) where (code',td) = e (lab+length code) st dep Beispiel 13.4 (Vier JavaLight+-Programme fu¨r die Fakult¨atsfunktion aus Java2.hs) Int x; read x; Int fact; fact=1; while x>1 fact=x*fact; x=x-1; write fact; Int f(Int x) if x<2 f=1; else f=x*f(x-1); Int x; read x; write f(x); 281 f(Int x,Int fact) if x<2 write fact; else f(x-1,fact*x) Int x; read x; f(x,1) Int f(Int x,Int g(Int x,Int y)) if x<2 f=1; else f=g(x,f(x-1,g)); Int g(Int x,Int y) g=x*y; Int x; read x; write f(x,g); javaToStack "prog" [n] u¨bersetzt jedes der obigen Programme in eine Befehlsfolge vom Typ [StackCom], legt diese in der Datei javacode ab und transformiert die Eingabeliste [n] in die Ausgabeliste [n!]. Der Zielcode des vierten Programms lautet wie folgt: 0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: Push Jump Move Push Push Jump Move Push Push Push Push (Con 0) (Con 68) TOP BA STP (Con 0) (Con 26) TOP BA (Dex STP 0) STP (Dex BA (-4)) (Dex BA (-3)) begin f begin g y x 282 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: Push BA Move (Dex Push (Con Jump (Dex Pop Save BA Pop Pop Pop Push (Dex Save (Dex Pop Pop Pop Jump (Dex Push (Dex Push (Con Cmp "<" JumpF 34 Push (Con Save (Dex Pop Jump (Con Push BA Push (Con (Dex BA 1) (-6)) STP 15) (Dex BA 1) (-5)) (Dex (Dex BA 1) (-4)) 0) (Dex BA 1) 1) g=@g(x,y) TOP (-1)) BA (-3)) 2) end g x x<2 1) (Dex BA 0) 0) f=1 65) 6) g g 283 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: PushA (Dex BA 1) Push (Dex BA (-3)) Push (Con 1) Sub Push BA Move (Dex BA 0) STP Push (Con 44) Jump (Con 2) Pop Save BA Pop Pop Pop Pop Pop Push (Dex (Dex BA 0) 0) Push (Dex BA (-3)) Push BA Move BA STP Push (Con 57) Jump (Con 6) Pop Save BA Pop Pop g x x-1 jump to f f(x-1,g) x jump to g 284 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: Pop Push (Dex BA 1) Save (Dex (Dex BA 0) 0) Pop Pop Pop Jump (Dex TOP (-1)) Push (Con 0) Jump (Con 79) Move TOP BA Push STP Push (Dex BA (-3)) Push (Dex BA (-4)) Mul Save (Dex (Dex BA 0) 1) Pop Pop Jump (Dex TOP (-1)) Push (Con 0) Read (Dex BA 2) Push BA Push (Con 70) PushA (Dex BA 1) Push (Dex BA 2) Push BA g(x,f(x-1,g)) f=g(x,f(x-1,g)) end f begin g x y x*y g=x*y end g read x g g g x 285 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: Move BA STP Push (Con 89) Jump (Con 2) Pop Save BA Pop Pop Pop Pop Pop Push (Dex BA 0) Write Pop jump to f f(x,g) write f(x,g) 286 14 Mehrp¨ assige Compiler Sei G = (S, BS, Z, R) eine CFG und A eine Σ(G)-Algebra. Wir kommen zuru¨ck auf die in Kapitel 12 behandelte Form Av1 × . . . × Avm → Aa1 × . . . × Aan der Tra¨germengen von A und sehen uns das Schema der Definition einer Operation von A mal etwas genauer an. O.B.d.A. setzen wir fu¨r die allgemeine Betrachtung voraus, dass alle Tr¨agermengen von A miteinander u¨bereinstimmen. Sei c : s1 × . . . × sk → s ein Konstruktor von Σ(G). Das Schema einer Interpretation cA : Ak → A von c in A offenbar wie folgt: cA(f1, . . . , fk )(x1, . . . , xm) = (t1, . . . , tn) where (x11, . . . , x1n) = f1(t11, . . . , t1m) ... (1) (xk1, . . . , xkn) = fk (tk1, . . . , tkm) Hier sind f1, . . . , fk , x1, . . . , xm, xi1, . . . , x1n, . . . , xk1, . . . , xkn paarweise verschiedene Variablen und e1, . . . , en, xi1, . . . , t1m, . . . , tk1, . . . , tkm Σ(G)-Terme. 287 Ist s → w0s1w1 . . . sk wk die Grammatikregel, aus der c hervorgeht, dann wird (1) oft als Liste von Zuweisungen an die Attribute der Sorten s, s1, . . . , sk geschrieben: s.a1 := t1 . . . s.an := tn s1.v1 := t11 . . . s1.vm := t1m ... sk .v1 := tk1 . . . sk .vm := tkm cA ist genau dann wohldefiniert, wenn fu¨r alle f1, . . . , fk , x1, . . . , xm (1) eine L¨osung in A hat, d.h., wenn es eine Belegung g der Variablen xi1, . . . , x1n, . . . , xk1, . . . , xkn in A gibt mit (g(xi1), . . . , g(xin)) = f1(g ∗(ti1), . . . , g ∗(tim)) fu¨r alle 1 ≤ i ≤ k. Hinreichend fu¨r die L¨osbarkeit ist die Zyklenfreiheit der Benutzt-Relation zwischen den Termen und Variablen von (1). Enth¨alt sie einen Zyklus, dann versucht man, die parallele Berechnung und Verwendung von Attributwerten in r hintereinander ausgefu¨hrte Schritte (“Pa¨sse”) zu zerlegen, so dass in jedem Schritt zwar nur einige Attribute berechnet bzw. benutzt werden, die Komposition der Schritte jedoch eine ¨aquivalente Definition von cA liefert. ¨ Notwendig wird die Zerlegung der Ubersetzung zum Beispiel dann, wenn die Quellsprache Deklarationen von Variablen verlangt, diese aber im Text des Quellprogramms bereits vor ihrer Deklaration benutzt werden du¨rfen. 288 Zerlegt werden mu¨ssen die Menge At = {v1, . . . , vm, a1, . . . , an} aller Attribute in r Teilmengen At1, . . . , Atr sowie jedes (funktionale) Argument fi von cA, 1 ≤ i ≤ k, in r Teilfunktionen fi1, . . . , fir derart, dass die Benutzt-Relation in jedem Pass azyklisch ist. Um die Benutzt-Relation zu bestimmen, erweitern wir die Indizierung der ¨außeren Variablen bzw. Terme von (1) wie folgt: cA(f1, . . . , fk )(x01, . . . , x0m) = (t(k+1)1, . . . , t(k+1)n) where (x11, . . . , x1n) = f1(t11, . . . , t1m) ... (xk1, . . . , xkn) = fk (tk1, . . . , tkm) (2) Am ersten Index i einer Variablen x oder eines Terms e entspricht einer Position in (2). Fu¨r jeden Konstruktor c und jedes Attributpaar (at, at0) ist der Abh¨ angigkeitsgraph depgraph(c)(at, at0) ⊆ {0, . . . , k} × {1, . . . , k + 1} fu¨r c und (at, at0) wie folgt definiert: 289 ∃ 1 ≤ i ≤ m, 1 ≤ j ≤ n : at = vi ∧ at0= aj (at vererbt, at0 abgeleitet) {(0, k + 1)} falls x0i in t(k+1)j vorkommt, ⇒ depgraph(c)(at, at0) = ∅ sonst, ∃ 1 ≤ i, j ≤ m : at = vi ∧ at0 = vj (at und at0 vererbt) ⇒ depgraph(c)(at, at0) = {(0, s) | 0 ≤ s ≤ k, x0i kommt in tsj vor}, ∃ 1 ≤ i ≤ n, 1 ≤ j ≤ m : at = ai ∧ at0 = vj (at abgeleitet, at0 vererbt) ⇒ depgraph(c)(at, at0) = {(r, s) | 0 ≤ r, s ≤ k, xri kommt in tsj vor}, ∃ 1 ≤ i, j ≤ n : at = ai ∧ at0 = aj (at und at0 abgeleitet) ⇒ depgraph(c)(at, at0) = {(r, k + 1) | 0 ≤ r ≤ k, xri kommt in t(k+1)j vor}. (2) hat genau dann eine L¨osung in A (die durch Auswertung der Terme von (2) berechnet werden kann), wenn • fu¨r alle Konstruktoren c von Σ(G), at, at0 ∈ At und (i, j) ∈ depgraph(c)(at, at0) i < j gilt. Ist diese Bedingung verletzt, dann wird At so in Teilmengen At1, . . . , Atr zerlegt, dass fu¨r alle at, at0 ∈ At entweder at in einem fru¨heren Pass als at0 berechnet wird oder beide Attribute in demselben Pass berechnet werden und die Bedingung erfu¨llen. 290 Fu¨r alle 1 ≤ p, q ≤ r, at ∈ Atp und at0 ∈ Atq muss also Folgendes gelten: p < q ∨ (p = q ∧ ∀ (i, j) ∈ depgraph(c)(at, at0) : i < j). (3) Fu¨r alle 1 ≤ p ≤ r und 1 ≤ i ≤ k sei {vi1 , . . . , vimp } = {v1, . . . , vm} ∩ Atp, {aj1 , . . . , ajnp } = {a1, . . . , an} ∩ Atp, πp : Av1 × . . . × Avm (x1, . . . , xm) πp0 : Aa1 × . . . × Aan (x1, . . . , xn) → 7→ → 7→ Avip1 × . . . × Avipmp (xip1 , . . . , xipmp ), Aajp1 × . . . × Aajpnp (xjp1 , . . . , xjpnp ) und fip : Avi1 × . . . × Avimp → Aaj1 × . . . × Aajnp die eindeutige Funktion, die das folgende Diagramm kommutativ macht: πp Av1 × . . . × Avm Avip1 × . . . × Avipmp fi fip g g πp0 Aa1 × . . . × Aan Aajp1 × . . . × Aajpnp 291 Die zu (2) ¨aquivalente Definition von cA mit r P¨assen lautet wie folgt: cA(f1, . . . , fk )(x01, . . . , x0m) = (t(k+1)1, . . . , t(k+1)n) where (x1j11 , . . . , x1j1n1 ) = f11(t1i11 , . . . , t1i1m1 ) ... Pass 1 (xkj11 , . . . , xkj1n1 ) = f11(tki11 , . . . , tki1m1 ) ... ... (x1jr1 , . . . , x1jrnr ) = f1r (t1ir1 , . . . , t1irmr ) ... Pass r (xkjr1 , . . . , xkjrnr ) = f1r (tkir1 , . . . , tkirmr ) 292 14.1 Der LAG-Algorithmus (Left-to-right-Attributed-Grammar) berechnet die kleinste Zerlegung von At, die (3) erfu¨llt, sofern eine solche existiert. Sei ats = At und constrs die Menge aller Konstruktoren von Σ(G). Ausgehend von der einelementigen Zerlegung [ats] ver¨andert check partition die letzten beiden Elemente (curr und next) der jeweils aktuellen Zerlegung, bis entweder next leer und damit eine Zerlegung gefunden ist, die (3) erfu¨llt, oder curr leer ist, was bedeutet, dass keine solche Zerlegung existiert. least_partition ats = reverse . check_partition [] [ats] check_partition next (curr:partition) = if changed then check_partition next' (curr':partition) else case (next',curr') of ([],_) -> curr':partition Zerlegung von ats, die (3) erfu ¨llt (_,[]) -> [] Es gibt keine Zerlegung, die (3) erfu ¨llt. _ -> check_partition [] (next':curr':partition) Die aktuelle Zerlegung wird um das Element nextfl erweitert. where (next',curr',changed) = foldl check_constr (next,curr,False) constrs 293 check_constr state c = foldl (check_atpair deps) state [(at,at') | at <- ats, at' <- ats, not $ null $ deps (at,at')] where deps = depgraph c check_atpair deps state atpair = foldl (check_dep atpair) state $ deps atpair check_dep (at,at') state@(next,curr,changed) (i,j) = if at' `elem` curr && ((at `elem` curr && i>=j) || at `elem` next) Die aktuelle Zerlegung next:curr:... verletzt (3). then (at':next,curr`minus`[at'],True) atfl wird vom vorletzten Zerlegungselement (curr) zum letzten (next) verschoben. else state 294 15 Monaden in Haskell 15.1 Typen und Typvariablen h¨ oherer Ordnung W¨ahrend bisher nur Typvariablen erster Ordnung vorkamen, sind Monaden Instanzen der Typklasse Monad, die eine Typvariable zweiter Ordnung enth¨alt. Typvariablen erster Ordnung werden durch Typen erster Ordnung instanziiert, das sind parameterlose Typen wie z.B. Int oder Bool. Typvariablen zweiter Ordnung werden durch Typen zweiter Ordnung instanziiert wie z.B. Term: data Term a = F a [Term a] | V a Demnach ist ein Typ erster Ordnung eine Menge und ein Typ zweiter Ordnung eine Funktion von einer Menge von Mengen in eine – i.d.R. andere – Menge von Mengen: Term bildet jede Menge A auf eine Menge von Ba¨umen ab, deren Knoteneintra¨ge Elemente von A sind. 295 Eine Typklasse mit einer Typvariablen zweiter Ordnung ist z.B. die Klasse Functor, deren Instanzen die in Kapitel 5 angesprochenen Funktoren implementieren: class Functor f where fmap :: (a -> b) -> f a -> f b Wie der Name andeutet, verallgemeinert fmap die polymorphe Funktion map :: (a -> b) -> [a] -> [b] von Listen auf beliebige Datentypen. Umgekehrt bilden Listen eine (Standard-)Instanz von Functor: instance Functor [ ] where fmap = map instance Functor Term where fmap f (F a ts) = F (f a) $ map (fmap f) ts fmap f (V a) = V (f a) Oft gibt es Anforderungen an die Instanzen einer Typklasse. Bei Functor sind es folgende Gleichungen: fmap id fmap (f . g) = = id fmap f . fmap g 296 15.2 Die Typklasse Monad (siehe Abschnitt 5.1) class Monad m where (>>=) :: m a -> (a -> m b) -> m b bind-Operatoren (>>) :: m a -> m b -> m b return :: a -> m a Einheit η fail :: String -> m a Wert im Fall eines Matchfehlers m >> m' = m >>= const m' class Monad m => MonadPlus m where mzero :: m a scheiternde Berechnung heißt zero in hugs mplus :: m a -> m a -> m a parallele Komposition heißt (++) in hugs Die Verwendung von Funktionen oder Instanzen von MonadPlus erfordert den Import des Standardmoduls Monad.hs, also die Zeile import Control.Monad am Anfang des Programms, das ihn verwendet. 297 Guards erlauben wie Exitbefehle imperativer Sprachen das Verlassen von Prozeduren: guard :: MonadPlus m => Bool -> m () guard b = if b then return () else mzero ¨ return und mzero sollten so definiert werden, dass die folgenden semantischen Aquivalenzen gelten: do guard True; m1; ...; mn ist ¨aquivalent zu do guard False; m1; ...; mn ist ¨aquivalent zu do m1; ...; mn mzero Funktionale Abh¨ angigkeiten Normalerweise mu¨ssen im Typ jeder Funktion einer Typklasse alle Typvariablen der Klasse vorkommen. Kommen nicht alle vor, dann mu¨ssen die fehlenden von den vorkommenden abh¨angig gemacht werden. So ist z.B. in der Typklasse class MonadPlus m => Compiler input m | errmsg :: input -> empty :: input -> ht :: input -> input -> m where m a Bool m (Char,input) am Anfang von Kapitel 16 die Abha¨ngigkeit input -> m der Funktion empty geschuldet. 298 Die Menge der im Programm definierten Instanzen einer Typklasse muss deren funktionale Abh¨angigkeiten tats¨achlich erfu¨llen. Zwei Instanzen von Compiler mit demselben inputTyp mu¨ssen also auch dieselbe Monade m haben. 15.3 Die Maybe-Monade implementiert die Exceptionmonade von Abschnitt 5.1 im Fall E = 1 als folgende Instanz der Typklasse Monad: data Maybe a = Just a | Nothing instance Monad Maybe where Just a >>= f = f a _ >>= _ = Nothing return = Just fail _ = mzero instance MonadPlus Maybe where mzero = Nothing Nothing `mplus` m = m m `mplus` _ = m 299 Rechnen mit partiellen Funktionen Eine partielle Funktion f : A (→ B wird in Haskell als totale Funktion von A nach Maybe(B) implementiert, die jedem Argument a ∈ A, das unter f einen definierten Wert hat, den Wert Just(f (a)) zuweist und allen anderen Argumenten den Wert Nothing. Sei g : B → Maybe(C) und a ∈ A. Nach obiger Definition von = in der Maybe-Monade sind folglich f (a) = g und do b ← f (a); g(b) korrekte Implementierungen von g(f (a)). Nach Definition von mzero bzw. mplus in der Maybe-Monade ist case f a of Just b | p b -> g b -> Nothing und case f a of Nothing -> g a b -> b ¨aquivalent zu do b <- f a guard $ p b g b ¨aquivalent zu f a `mplus` g a Die Exceptionmonade von Abschnitt 5.1 mit einer beliebigen Fehlermenge E und ausgezeichnetem Fehlerelement err kann in Haskell als folgende Standard-Instanz der Typklasse Monad durch den Datentyp Either implementiert werden: 300 data Either e a = Left e | Right a instance Monad (Either e) where Left e >>= _ = Left e Right a >>= f = f a return = Right instance MonadPlus (Either e) where mzero = Left err Left _ `mplus` m = m m `mplus` _ = m 15.4 Die Listenmonade (siehe Abschnitt 5.1) ist in Haskell standardm¨aßig als folgende Instanzen der Typklassen Monad und MonadPlus durch den Listen-Datentyp implementiert: instance Monad [ ] where (>>=) = flip concatMap return a = [a] fail _ = mzero instance MonadPlus [ ] where mzero = [] mplus = (++) 301 Rechnen mit nichtdeterministischen Funktionen Eine nichtdeterministische Funktion f : A → P(B) hat eine Potenzmenge als Wertebereich. Deren Elemente werden in Haskell oft durch Listen dargestellt und damit f als Funktion vom Typ A → B ∗. Nach Definition von >>= in der Listenmonade ist fu¨r alle g : B → C ∗ concat [g b | b <- f a] ¨aquivalent zu und damit zu f a >>= g do b <- f a; g b also eine Implementierung von g(f (a)). Nach Definition von mzero bzw. mplus in der Listenmonade ist do b <- f a; guard (p b); g b ¨aquivalent zu concat [g b | b <- f a, p b] und do a <- s; let b = f a; guard (p b); [h b] ¨aquivalent zu [h b | a <- s, let b = f a, p b] 15.5 Transitionsmonaden (siehe Abschnitt 5.1) 302 Trans state a state -> (a,state) state = system state total transitions IO a TransM state m a state -> m (a,state) m is [ ] m is Maybe m is Either e partial transitions nondeterministic transitions functions with exception values Monad m MonadPlus m compilers returning a list of target objects Compiler input m input is String m is [ ] compilers returning a target object or an erroneous input position input is (Pos,String) m is Either Pos TransM input m a input -> m (a,input) 303 Eine Monade fu ¨ r totale Transitionsfunktionen newtype Trans state a = T {run :: state -> (a,state)} instance Monad (Trans state) where T trans >>= f = T $ \st -> let (a,st') = trans st in run (f a) st' return a = T $ \st -> (a,st) Hier komponiert der bind-Operator >>= die Zustandstransformationen trans und trans0 sequentiell. Dabei erh¨alt trans0 die von trans erzeugte Ausgabe a als Eingabe. Die IO-Monade kann man sich vorstellen als Instanz von des Datentyps Trans, wobei die Zustandsmenge state alle m¨oglichen Systemzust¨ande umfasst. Verwenden kann man die IO-Monade nur indirekt u¨ber Standardfunktionen wie den folgenden: 304 readFile :: String -> IO String readFile "source" liest den Inhalt der Datei source und gibt ihn als String zuru ¨ck. writeFile :: String -> String -> IO () writeFile "target" schreibt einen String in die Datei target. putStr :: String -> IO () putStr str schreibt str ins Shell-Fenster. putStrLn :: String -> IO () putStrLn str schreibt str ins Shell-Fenster und geht in die n¨achste Zeile. getLine :: IO String getLine liest den eingebenen String und geht in die n¨achste Zeile. 305 IO-Fehlerbehandlung readFileAndDo :: String -> (String -> IO ()) -> IO () readFileAndDo file continue = do str <- readFile file `catch` const (return "") if null str then putStrLn $ file++" does not exist" else continue str readFileAndDo(file)(continue) liest den Inhalt der Datei file und u¨bergibt ihn als String str zur Weiterverarbeitung an die Funktion continue. catch hat den Typ IO a -> (IOError -> IO a) -> IO a. catch(m)(f ) f¨angt einen bei der Ausfu¨hrung von m auftretenden IO-Fehler err ab, indem f auf err angewendet wird. test :: (Read a,Show b) => (a -> b) -> IO () test f = readFileAndDo "source" $ writeFile "target" . show . f . read test(f ) liest ein Argument der Funktion f aus der Datei source, wendet f darauf an und legt den berechneten Wert in der Datei target ab. 306 Kommando-Schleifen loop :: IO () loop = do putStrLn "Hello!" putStrLn "Enter an integer x!" str <- getLine let x = read str lokale Definition (gilt bis zur n¨achsten lokalen Definition von x if x < 5 then do putStr "x < 5" else do putStrLn $ "x = "++show x loop then und else mu ¨ssen hinter if stehen! 307 Mehrere Ausgabefunktionen des Painter rufen Varianten der folgenden Schleife auf, die bei jeder Iteration ein graphisches Objekt a aus der Datei file liest, gem¨aß eingegebener Faktoren skaliert, in SVG-Code u¨bersetzt und diesen in die Datei file.svg schreibt: readFileAndDraw :: Read a => String -> (Float -> Float -> a -> (String,Pos)) -> IO () readFileAndDraw file draw = readFileAndDo file $ scale . read where scale a = do putStrLn "Enter a horizontal and a vertical scaling factor!" str <- getLine let strs = words str when (length strs == 2) $ do let [hor,ver] = map read strs (code,size) = draw hor ver a writeFile (file++".svg") $ svg code size scale a 308 Die Huckepack-Transitionsmonade (siehe Abschnitt 5.1) hat in Haskell-Bibliotheken den Namen StateT (state transformer) und unterscheidet sich von Trans dadurch, dass ihr Wertetyp (a,state) in eine Monade m eingebettet ist: newtype TransM state m a = TM {runM :: state -> m (a,state)} instance Monad m => Monad (TransM state m) where TM trans >>= f = TM $ \st -> do (a,st) <- trans st runM (f a) st return a = TM $ \st -> return (a,st) Die Elemente von TransM (state)(Maybe) liefern deterministische Automaten: state ist die ¨ Zustandsmenge, die Ausgabe- bzw. Ubergangsfunktion ist durch die partiellen Funktionen first ◦ runM bzw. snd ◦ runM gegeben. Die Elemente von TransM (state)([ ]) liefern nichtdeterministische Automaten: state ist die ¨ Zustandsmenge, die Ausgabe- bzw. Ubergangsrelation ist durch die nichtdeterministischen Funktionen first ◦ runM bzw. snd ◦ runM gegeben. 309 15.6 Monaden-Kombinatoren when :: Monad m => Bool -> m () -> m () when b m = if b then m else return () msum :: MonadPlus m => [m a] -> m a msum = foldr mplus mzero heißt concat in hugs msum verallgemeinert mplus von zwei auf beliebig viele Prozeduren. sequence :: Monad m => [m a] -> m [a] sequence (m:ms) = do a <- m; as <- sequence ms; return $ a:as sequence _ = return [] heißt accumulate in hugs sequence(ms) fu¨hrt die Prozeduren der Liste ms hintereinander aus. Wie bei some(m) und many(m) werden die dabei erzeugten Ausgaben aufgesammelt. Im Gegensatz zu some(m) und many(m) ist die Ausfu¨hrung von sequence(ms) erst beendet, wenn ms leer ist und nicht schon dann, wenn eine Wiederholung von m scheitert. sequence_ :: Monad m => [m a] -> m () sequence_ = foldr (>>) $ return () heißt sequence in hugs sequence (ms) arbeitet wie sequence(ms), vergisst aber die erzeugten Ausgaben. 310 Die folgenden Funktionen fu¨hren mit map bzw. zipW ith transformierte Prozedurlisten aus: mapM :: Monad m => (a -> m b) -> [a] -> m [b] mapM f = sequence . map f mapM_ :: Monad m => (a -> m b) -> [a] -> m () mapM_ f = sequence_ . map f zipWithM :: Monad m => (a -> b -> m c) -> [a] -> [b] -> m [c] zipWithM f s = sequence . zipWith f s zipWithM_ :: Monad m => (a -> b -> m c) -> [a] -> [b] -> m () zipWithM_ f s = sequence_ . zipWith f s 311 16 Monadische Compiler Sei G = (S, BS, Z, R) eine CFG, X = Z ∪ Compiler fu¨r G eine Funktionsmenge S BS. Laut Kapitel 5 ist ein generischer ∗ compileG = {compileA G : X → M (A) | A ∈ AlgΣ(G) }. Folglich ließe sich compileG als Objekt der String-Instanz TransM String m a der Huckepack-Transitionsmonade implementieren. ¨ Oft erfordern die Ubersetzung oder auch die Ausgabe differenzierter Fehlermeldungen zus¨atzliche Informationen u¨ber die Eingabe wie z.B. Zeilen- und Spaltenpositionen von Zeichen des Eingabetextes, um Syntaxfehler den Textstellen, an denen sie auftreten k¨onnen, zuzuordnen. Den Eingabetyp auf String zu beschra¨nken ist dann nicht mehr ada¨quat. Deshalb ersetzen wir String durch die Typvariable input und formulieren die Anforderungen an input zusammen mit korrespondierenden Anforderungen an die Monade m als Unterklasse Compiler von MonadPlus (siehe das Diagramm am Anfang von Abschnitt 15.5): 312 class MonadPlus m => Compiler input m where errmsg :: input -> m a empty :: input -> Bool ht :: input -> m (Char,input) erstes Zeichen und Resteingabe errmsg behandelt fehlerhafte Eingaben. empty pru¨ft, ob die Eingabe leer ist. Wenn nicht, dann liefert ht das erste Zeichen der Eingabe und die Resteingabe. Ein Funktionstyp in einer Typklasse C sollte stets alle Typvariablen von C enthalten. Z.B. br¨auchten empty und ht eigentlich nur den Wertebereich Bool bzw. (Char, input). Dies ¨ wu¨rde aber zu einem Typkonflikt bei der Ubersetzung einer Funktion f : Compiler (input)(m) ⇒ . . . m . . . fu¨hren: Der Haskell-Interpreter wu¨rde neue Typvariablen input0 und m0 erzeugen und z.B. empty den Typ Compiler (input 0)(m 0) ⇒ input0 → Bool zuordnen, ohne m0 mit m gleichzusetzen. 313 Zwei Instanzen der Typklasse Compiler Fu¨r mehrere korrekte Ausgaben, aber nur einer m¨ogliche Fehlermeldung: instance Compiler String [ ] where errmsg _ = [] empty = null ht (c:str) = [(c,str)] Fu¨r h¨ochstens eine korrekte Ausgabe, aber mehrere m¨ogliche Fehlermeldungen: type Pos = (Int,Int) instance MonadPlus (Either Pos) where mzero = Left (0,0) Left _ `mplus` m = m m `mplus` _ = m instance Compiler (Pos,String) (Either Pos) where errmsg = Left . fst Ausgabe der Fehlerposition empty = null . snd ht ((i,j),c:rest) = Right (c,(pos,rest)) where pos = if c == '\n' then (i+1,1) else (i,j+1) 314 Hier sind die Eingaben vom Typ (Pos, String). Die erste Komponente (i, j) ist die Anfangsposition (i-te Zeile, j-te Spalte) der String-Komponente innerhalb der gesamten Eingabe. Folglich gibt ht(c : str) neben dem Reststring str nach dem Lesen des Zeichens c die korrekte Anfangsposition von str zuru¨ck. Scheitert ein mit dieser Compiler-Instanz gebildeter Compiler, dann ruft er errmsg mit der Eingabeposition auf, an welcher er den Fehler erkannt hat, der ihn scheitern ließ. 16.1 Compilerkombinatoren runC(comp)(input) fu¨hrt den Compiler comp auf der Eingabe input aus und scheitert, falls comp eine nichtleere Resteingabe zuru¨ckla¨sst: runC :: Compiler input m => TransM input m a -> input -> m a runC comp input = do (a,input) <- runM comp input if empty input then return a else errmsg input 315 runPS(comp)(str)(contL)(contR) wendet den Compiler comp der letzten o.g. CompilerInstanz auf den Eingabestring str an und f¨ahrt bei korrekter Eingabe mit der Anwendung von contR auf die Ausgabe fort, im Fehlerfall jedoch mit der Anwendung von contL auf die Fehlerposition: runPS :: TransM (Pos,String) (Either Pos) a -> String -> (Pos -> b) -> (a -> b) -> b runPS comp str contL contR = case runC comp ((1,1)::Pos,str) of Left (pos::Pos) -> contL pos Right a -> contR a posError :: Pos -> IO () posError pos = putStrLn $ "error at position "++show pos cplus :: Compiler input m => TransM input m a -> TransM input m a -> TransM input m a TM f `cplus` TM g = TM $ lift mplus f g csum :: Compiler input m => [TransM input m a] -> TransM input m a csum = foldr cplus TM errmsg 316 some(comp) und many(comp) wenden comp auf die Eingabe an. Akzeptiert comp ein Pr¨afix der Eingabe, dann wird comp auf die Resteingabe angewendet und dieser Vorgang wiederholt, bis comp scheitert. Die Ausgabe der drei Compiler ist die Liste der Ausgaben der einzelnen Iterationen von comp: some, many :: Compiler input m => TransM input m a -> TransM input m [a] some comp = do a <- comp; as <- many comp; return $ a:as many comp = some comp `cplus` return [] some(comp) scheitert, wenn bereits die erste Iteration von comp scheitert. many(comp) scheitert in diesem Fall nicht, sondern liefert die leere Liste von Ausgaben: cguard :: Compiler input m => Bool -> TransM input m () cguard b = if b then return () else TM errmsg sat(comp)(f ) pru¨ft, ob die von comp erzeugte Ausgabe die Bedingung f erfu¨llt. Falls nicht, setzt sat(comp)(f ) eine von der jeweiligen Eingabe abha¨ngige Fehlermeldung ab: sat :: Compiler input m => TransM input m a -> (a -> Bool) -> TransM input m a 317 sat comp f = TM $ \input -> do result@(a,_) <- runM comp input if f a then return result else errmsg input getChr scheitert, wenn die Eingabe leer ist. Andernfalls gibt getChr das erste Zeichen der Eingabe aus: getChr :: Compiler input m => TransM input m Char getChr = TM $ \input -> if empty input then errmsg input else ht input token(comp) erlaubt vor und hinter von comp akzeptierten Eingaben Leerzeichen, Zeilenumbru¨che und Tabulatoren: token :: Compiler input m => TransM input m a -> TransM input m a token comp = do space; a <- comp; space; return a where space = many $ sat getChr isDelim isDigit isLetter isSpecial isDelim = = = = (`elem` (`elem` (`elem` (`elem` ['0'..'9']) ['a'..'z']++['A'..'Z']) "()<>;=!+-*/^") " \n\t") 318 16.2 Monadische Scanner erkennen Symbole und u¨bersetzen diese in Zeichen oder Strings: char, string und relation erkennen einzelne Zeichen, Strings bzw. Relationssymbole: char :: Compiler input m => Char -> TransM input m Char char chr = sat getChr (== chr) string :: Compiler input m => String -> TransM input m String string = mapM char relation :: Compiler input m => TransM input m String relation = csum $ map string $ words "<= >= < > == !=" tchar :: Compiler input m => Char -> TransM input m Char tchar = token . char tstring :: Compiler input m => String -> TransM input m String tstring = token . string 319 16.3 Compiler fu ¨ r Basismengen ersetzen Elemente von Basismengen (Wahrheitswerte, Zahlen, Identifier) in Elemente entsprechender Haskell-Typen. bool :: Compiler input m => TransM input m Bool bool = csum [do string "True"; return True, do string "False"; return False] nat,int :: Compiler input m => TransM input m Int nat = do ds <- some $ sat getChr isDigit; return $ read ds int = csum [nat, do char '-'; n <- nat; return $ -n] identifier :: Compiler input m => TransM input m String identifier = do first <- sat getChr isLetter rest <- many $ sat getChr $ not . lift (||) isSpecial isDelim let x = first:rest guard $ x `notElem` words "True False if else while" return x 320 16.4 Monadische LL-Compiler Sei G = (S, BS, Z, R) eine nicht-linksrekursive CFG. Wir setzen voraus, dass S eine aus¨ gezeichnete Sorte start enth¨alt, und implementieren die Ubersetzungsfunktionen ∗ compileA G : X → M (alg), alg ∈ AlgΣ(G), und ihre Hilfsfunktionen (siehe Kapitel 6) wie folgt: Sei S = {s1, . . . , sk }. compile_G :: Compiler input m => Alg s1...sk -> input -> m start compile_G = runC . trans_start Fu¨r alle B ∈ BS ∪ Z sei trans_B :: Compiler input m => TransM input m B gegeben. Seien s ∈ S und r1, . . . , rm die Regeln von R mit linker Seite s. trans_s :: Compiler input m => TransM input m s trans_s alg = csum [try_r1,...,try_rm] where ... try_ri = do a1 <- trans_e1; ...; an <- trans_en return $ f_ri alg a_i1 ... a_ik ... 321 wobei ri = (s → e1 . . . en) und {i1, . . . , ik } = {1 ≤ i ≤ n | ei ∈ S ∪ BS}. Beispiel Aus der abstrakten Syntax der Grammatik SAB von Beispiel 4.5 ergibt sich die folgende Haskell-Implementierung der Klasse aller Σ(SAB)-Algebren: data SAB s a b = SAB {f_1 :: b -> s, f_2 :: a -> s, f_3 :: s, f_4 :: s -> a, f_5 :: a -> a -> a, f_6 :: s -> b, f_7 :: b -> b -> b,} Nach obigem Schema liefern die Regeln r1 = S → aB, r2 = S → bA, r3 = S → , r4 = A → aS r5 = A → bAA, r6 = B → bS, r7 = B → aBB von SAB die folgende Haskell-Version des generischen SAB-Compilers von Beispiel 6.1: compS :: Compiler input m => SAB s a b -> TransM input m s compS alg = csum [do char 'a'; c <- compB alg; return $ f_1 alg c, do char 'b'; c <- compA alg; return $ f_2 alg c, return $ f_3 alg] compA :: Compiler input m => SAB s a b -> TransM input m a compA alg = csum [do char 'a'; c <- compS alg; return $ f_4 alg c, do char 'b'; c <- compA alg; d <- compA alg; return $ f_5 alg c d] 322 compB :: Compiler input m => SAB s a b -> TransM input m b compB alg = csum [do char 'b'; c <- compS alg; return $ f_6 alg c, do char 'a'; c <- compB alg; d <- compB alg; return $ f_7 alg c d] In Compiler.hs steht dieser Compiler zusammen mit der Zielalgebra SABcount von Beispiel 4.5. Fu¨r m = Maybe und alle w ∈ {a, b}∗ und i, j ∈ N gilt compileSAB (SABcount)(w) = Just(i, j) genau dann, wenn i und j die Anzahl der Vorkommen von a bzw. b in w ist. o 16.5 Generischer JavaLight-Compiler (siehe Java.hs) compJava :: Compiler input m => JavaLight s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 -> TransM input m s1 compJava alg = commands where commands = do c <- command csum [do cs <- commands; return $ seq_ alg c cs, return $ embed alg c] command = csum [do tstring "if"; e <- disjunctC; c <- command csum [do tstring "else"; c' <- command return $ cond alg e c c', return $ cond1 alg e c], 323 do tstring "while"; e <- disjunctC; c <- command return $ loop alg e c, do tchar '{'; cs <- commandsC; tchar '}' return $ block alg cs, do x <- token identifier; tchar '='; e <- sumC tchar ';'; return $ assign alg x e] sumC = do e <- prodC; f <- sumsectC; return $ sum_ alg e f sumsectC = csum [do op <- tstring "+" `cplus` tstring "-" e <- prodC; f <- sumsectC return $ sumsect alg op e f, return $ nilS alg] prodC = do e <- factor; f <- prodsectC; return $ prod alg e f prodsectC = csum [do op <- tstring "*" `cplus` tstring "/" e <- factor; f <- prodsectC return $ prodsect alg op e f, return $ nilP alg] factor = csum [do i <- token int; return $ embedI alg i, do x <- token identifier; return $ var alg x, do tchar '('; e <- sumC; tchar ')' return $ encloseS alg e] disjunctC = do e <- conjunctC csum [do tstring "||"; e' <- disjunctC return $ disjunct alg e e', return $ embedC alg e] 324 conjunctC = do e <- literal csum [do tstring "&&"; e' <- conjunctC return $ conjunct alg e e', return $ embedL alg e] literal = csum [do b <- token bool; return $ embedB alg b, do tchar '!'; e <- literal; return $ not_ alg e, do e <- sumC; rel <- token relation; e' <- sumC return $ atom alg e rel e', do tchar '('; e <- disjunctC; tchar ')' return $ encloseD alg e] 16.6 Korrektheit des JavaLight-Compilers bzgl. javaState Sei Store comS expS bexpS sectS = = = = = String → Z, {Commands, Command }, {Sum, Prod , Factor }, {Disjunct, Conjunct, Literal }, {Sumsect, Prodsect} 325 und s eine Sorte von JavaLight. Nach 11.8 bzw. 12.5 gilt ∗ javaStack s = Z → StackCom , Store (→ Store Store (→ Z javaState s = Store (→ 2 Store (→ (Z → Z) falls falls falls falls s ∈ comS , s ∈ expS , s ∈ bexpS , s ∈ sectS . Sei A = javaStack , B = javaState und s eine Sorte von JavaLight. Gema¨ß Diagramm (9) und (2) von Kapitel 5 bzw. 12 ist compJava(A) ein Compiler fu¨r JavaLight bzgl. B, wenn fu¨r alle Sorten s von Σ(JavaLight) das folgende Diagramm kommutiert: compJava(A)s initializes As Z × As L(JavaLight)s compJava(B)s g Bs (1) encodes execute g Mach s ≺ (2) execute0 λ(n, f ). (n, f (n)) g Z × StackCom ∗ Diagramm (2) entspricht Diagramm (2) von Kapitel 12. execute0 wurde in Abschnitt 12.5 unter dem Namen execute implementiert. 326 Mach s, initializes und encodes sind wie folgt definiert: • Mach s =def (State (→ State), wobei State = Z∗ × Store × Z (siehe 12.5). • Fu¨r alle f ∈ As, initializes(f ) = (0, f ). • Fu¨r alle g ∈ Bs und (stack, store) ∈ State, (stack, g(store)) falls s ∈ comS und g(store) definiert ist, (g(store) : stack, store) falls s ∈ expS ∪ bexpS und g(store) definiert ist, encodes(g) = (g(store)(head(stack)) : tail(stack), store) falls s ∈ sectS , stack 6= und g(store) definiert ist, undefiniert sonst. (1) beschreibt vor allem die folgenden Zusammenh¨ange zwischen dem Compiler compJava(A) und dem Interpreter execute seiner Zielsprache: • Sei com ein JavaLight-Programm einer Sorte s ∈ comS . Wird com von compJava(A)s in die Befehlsfolge cs u¨bersetzt und beginnt die Ausfu¨hrung von cs mit dem ersten Befehl von cs (n = 0) im Zustand state, dann endet sie in einem Zustand, dessen Speicherkomponente mit der von encodes(compJava(B)s(com))(state) u¨bereinstimmt. 327 • Sei exp ein JavaLight-Programm einer Sorte s ∈ expS ∪ bexpS . Wird exp von compJava(A)s in die Befehlsliste cs u¨bersetzt und beginnt die Ausfu¨hrung von cs mit dem ersten Befehl von cs (n = 0) im Zustand state, dann endet sie in einem Zustand, dessen oberstes Kellerelement mit dem von encodes(compJava(B)s(exp))(state) u¨bereinstimmt. Unter der Voraussetzung, dass der Parser compJava(javaTerm) ein Parser fu¨r JavaLight ist (siehe Kapitel 5), ist die Kommutativit¨at von (1) ¨aquivalent zu der des folgenden Diagramms: javaTerm s fold B s g Bs fold A s (3) encodes As execute g Mach Wie in Kapitel 5 erw¨ahnt wurde, ist (3) kommutativ, wenn Mach eine JavaLight-Algebra ist und die mehrsortigen Funktionen encode und execute JavaLight-homomorph sind. Um das beweisen zu k¨onnen, fehlt uns aber neben der Interpretation aller Konstruktoren von Σ(JavaLight) in Mach auch noch die des Konstruktors loop in B (siehe 11.8). Wir werden darauf im Abschnitt 19.1 bzw. 19.2 zuru¨ckkommen. 328 16.7 Testumgebung fu ¨ r den JavaLight-Compiler (siehe Java.hs) javaToAlg(file)(n) l¨adt ein Quellprogramm vom Typ commands aus der Datei file und u¨bersetzt es in verschiedene (rot markierte) Zielalgebren: javaToAlg :: String -> Int -> IO () javaToAlg file n = readFileAndDo file act where siehe Transitionsmonaden act str = case n of 1 -> run javaTerm $ draw "javaterm" siehe 11.5 2 -> run javaWord $ writeFile "javasource" siehe 11.7 3 -> run javaDeri $ draw "javaderi" siehe 11.9 4 -> run javaList $ siehe 12.4 \(a::BIS) -> writeFile "javalist" $ a True 0 5 -> run javaState loop siehe 11.8 where loop (a::St Store) = do (vars,state) <- input showStore vars (a state) $ loop a 6 -> run javaStack $ siehe 12.5 \(a::LCom) -> do let code = a 0 writeFile "javatarget" $ showCode code loopStack code where run alg = runPS (compJava alg) str posError 329 showCode :: [StackCom] -> String showCode = concat . zipWith f [0..] where f n c = '\n':replicate (5-length lab) ' '++lab++": "++show c where lab = show n draw :: Show a => String -> a -> IO () draw file a = do writeFile file $ show a drawTermC file erzeugt SVG-Code eines Baumes mit farbigen Knoten input :: IO ([String],Store) input = do putStrLn "Enter variables!"; varstring <- getLine putStrLn "Enter values!"; valstring <- getLine let vars = words varstring vals = map read $ words valstring vals' = vals++replicate (length vars-length vals) 0 return (vars, listsToFun 0 vars vals') showStore :: Show a => [String] -> (String -> a) -> IO () -> IO () showStore vars store continue = when (nonempty vars) $ do mapM_ g vars; continue where g x = putStrLn $ x++" = "++show (store x) loopStack :: [StackCom] -> IO () loopStack code = do (vars,store) <- input let (_,store',_) = execute code ([],store,0) showStore vars store' $ loopStack code 330 java(file)(5) und java(file)(6) starten die Schleifen loop bzw. loopStack, die in jeder Iteration eine Variablenbelegung einlesen und den Zustandstransformation a bzw. execute(code) (siehe 12.5) darauf anwenden. 16.8 Generischer XMLstore-Compiler (siehe Compiler.hs) compXML :: Compiler input m => XMLstore s1 s2 s3 s4 s5 s6 s7 s8 s9 -> TransM input m s1 compXML alg = storeC where storeC = do tstring "<store>" csum [do stck <- stock; return $ store alg stck, do ords <- ordersC; stck <- stock return $ storeO alg ords stck] where stock = do tstring "<stock>"; stck <- stockC tstring "</stock>" tstring "</store>"; return stck ordersC = do (p,is) <- order csum [do os <- ordersC return $ orders alg p is os, return $ embedO alg p is] order = do tstring "<order>"; tstring "<customer>" p <- personC; tstring "</customer>" is <- itemsC; tstring "</order>" return (p,is) 331 personC emailsC emailC itemsC item stockC iqs = do tstring "<name>"; name <- text; tstring "</name>" csum [do ems <- emailsC return $ personE alg name ems, return $ person alg name] = csum [do em <- emailC; ems <- emailsC return $ emails alg em ems, return $ none alg] = do tstring "<email>"; em <- text; tstring "</email>" return $ email alg em = do (id,price) <- item csum [do is <- itemsC return $ items alg id price is, return $ embedI alg id price] = do tstring "<item>"; id <- idC; tstring "<price>" price <- text; tstring "</price>" tstring "</item>"; return (id,price) = do (id,qty,supps) <- iqs csum [do is <- stockC return $ stock alg id qty supps is, return $ embedS alg id qty supps] = do tstring "<item>"; id <- idC; tstring "<quantity>" qty <- token int; tstring "</quantity>" supps <- suppliersC; tstring "</item>" return (id,qty,supps) 332 suppliersC = csum [do tstring "<supplier>"; p <- personC tstring "</supplier>" return $ supplier alg p, do stck <- stockC; return $ parts alg stck] idC = do tstring "<id>"; t <- text; tstring "</id>" return $ id_ alg t text :: Compiler input m => TransM input m String text = do strs <- some $ token $ some $ sat getChr $ lift (&&) (/= '<') $ not . isDelim return $ unwords strs 333 17 Induktion, Coinduktion und rekursive Gleichungen Induktion Sei CΣ = (S, BS, BF, C) eine konstruktive Signatur. Die zentrale Methode zum Beweis von Eigenschaften der initialen CΣ-Algebra - unabh¨angig von deren konkreter Repr¨asentation – ist die Induktion. Ihre Korrektheit (soundness) folgt aus Satz 3.2 (3): Sei A eine initiale CΣ-Algebra. A ist die einzige Unteralgebra von A. (1) Sei ϕ eine pr¨adikatenlogische Formel, die eine Eigenschaft der Elemente von A beschreibt, und B = {a ∈ A | a erfu¨llt ϕ}. Die Frage, ob ϕ fu¨r alle Elemente von A gilt, reduziert sich wegen (1) auf die Frage, ob B eine Σ-Unteralgebra von A ist, ob also fu¨r alle c : e → s ∈ C cA(Be) ⊆ Bs gilt. (2) Induktion heißt demnach: die Gu¨ltigkeit von ∀ x : ϕ(x) in TCΣ aus einem Beweis von (2) schließen. Induktion erlaubt es u.a., die Korrektheit als Gleichungen formulierter rekursiver Programme zu beweisen, indem man zeigt, dass deren gewu¨nschte Semantik die Gleichungen lo¨st: 334 Rekursive Ψ-Gleichungssysteme Sei CΣ = (S, BS, BF, C) eine konstruktive und DΣ = (S 0, BS 0, BF 0, D) eine destruktive Signatur. Ist S = S 0, dann nennen wir Ψ = (CΣ, DΣ) eine Bisignatur. Eine Menge E = {dc(x1, . . . , xnc ) = td,c | c : e1 × · · · × enc → s ∈ C, d : s → e ∈ D} von (CΣ ∪ DΣ)-Gleichungen (siehe Kapitel 3) mit folgenden Eigenschaften heißt rekursives Ψ-Gleichungssystem: • Fu¨r alle d ∈ D und c ∈ C, free(td,c) ⊆ {x1, . . . , xnc }. • C ist die Vereinigung disjunkter Mengen C1 und C2. • Fu¨r alle d ∈ D, c ∈ C1 und Teilterme du von td,c ist u eine Variable und td,c ein Term ohne Elemente von C2. • Fu¨r alle d ∈ D, c ∈ C2, Teilterme du und Pfade p (der Baumdarstellung) von td,c besteht u aus Destruktoren und einer Variable und kommt auf p ho¨chstens einmal ein Element von C2 vor. 335 Induktive Lo ¨sungen Sei E ein rekursives Ψ-Gleichungssystem und A eine CΣ-Algebra. Eine induktive L¨ osung von E in A ist eine (CΣ ∪ DΣ)-Algebra, deren CΣ-Redukt mit A u¨bereinstimmt und die E erfu¨llt. Satz 17.1 Sei C2 leer und A eine initiale CΣ-Algebra. Dann hat E genau eine induktive L¨osung in A. Beweis. Zun¨achst wird die Existenz einer induktiven L¨osung von E in A durch Induktion bewiesen. Wir zeigen, dass B mit Bs = {a ∈ As | fu¨r alle d : s → e ∈ D ist dA(a) definiert}, s ∈ S, eine Unteralgebra von A ist. Man k¨onnte das sehr schnell zeigen, wenn man voraussetzt, dass A mit der initialen Termalgebra TCΣ u¨bereinstimmt. Um aber deutlich zu machen, dass der Satz fu¨r jede initiale Algebra A gilt, verwenden wir stattdessen Lambeks Lemma (siehe Kapitel 3), das fu¨r initiale Algebren A folgendermaßen lautet: Sei {c1 : e1 → s, . . . , cn : en → s} = {c ∈ C | ran(c) = s}. Die Summenextension A [cA 1 , . . . , cn ] : Ae1 ] · · · ] Aen → As (a, i) 7→ cA i (a) 336 von Cs ist bijektiv. Es gibt also eine Funktion dA s : As → Ae1 ] · · · ] Aen A A A A A mit (3) [cA 1 , . . . , cn ] ◦ ds = idAs und (4) ds ◦ [c1 , . . . , cn ] = idAe1 ]···]Aen . A Sei also a ∈ As. Wegen (3) gibt es 1 ≤ i ≤ n und b ∈ Aei mit dA s (a) = (b, i) und ci (b) = a. Sei d : s → e0 ∈ D und b = (a1, . . . , anci ) ∈ Bei , d.h. fu¨r alle 1 ≤ j ≤ nci ist dA(aj ) definiert. Dann ist auch a0 = val[a1/x1, . . . , anci /xnci ]∗(td,ci ) definiert. Um dA an der Stelle a durch a0 zu definieren, bleibt zu zeigen, dass die Darstellung von a als Applikation cA i (b) eines Konstruktors eindeutig ist. 0 Sei 1 ≤ j ≤ n und b0 ∈ Aej mit a = cA j (b ). Dann ist (4) A A 0 A A A 0 0 0 (b, i) = dA s (a) = ds (cj (b )) = ds ([c1 , . . . , cn ](b , j)) = (idAe1 +···+Aen )(b , j) = (b , j), also b = b0 und ci = cj . 0 A Folglich liefert dA(cA i (b)) = a eine eindeutige Definition von d an der Stelle a. Damit gilt (2) und wir schließen aus (1), dass dA(a) fu¨r alle a ∈ As (eindeutig) definiert ist. Auch die Eindeutigkeit der induktiven Lo¨sung von E in A la¨sst sich durch Induktion zeigen: Seien A1, A2 zwei L¨osungen von E in A. 337 Wir zeigen, dass B mit Bs = {a ∈ As | fu¨r alle d : s → e ∈ D, dA1 (a) = dA2 (a)} eine Unteralgebra von A ist. Sei c : e → s ∈ C, d : s → e0 ∈ D und a = (a1, . . . , anc ) ∈ Be, d.h. fu¨r alle 1 ≤ i ≤ nc ist dA1 (ai) = dA2 (ai). Daraus folgt fu¨r val1 : V → A1 und val2 : V → A2 mit val1(xi) = val2(xi) = ai: dA1 (cA(a)) A1 l¨ost E = val1∗(td,c) = val2∗(td,c) A2 l¨ost E = dA2 (cA(a)). Damit gilt (2) und wir schließen aus (1), dass dA1 mit dA2 u¨bereinstimmt. o Beispiel 17.2 (CΣ = Nat; siehe 2.1) Seien DΣ = ({nat}, ∅, ∅, {plus : nat → natnat, sum : nat → nat}), Ψ = (Nat, DΣ) und x, y Variablen. Dann bilden die Gleichungen plus(zero) plus(succ(x)) sum(zero) sum(succ(x)) = = = = λx.x, λy.succ(plus(x)(y)), zero, plus(sum(x))(succ(x)) 338 ein rekursives Ψ-Gleichungssystem E, das in der initialen Nat-Algebra A mit Anat = N, 0 zeroA = 0 und succA = λn.n + 1 die induktive L¨osung A0 hat mit plusA = λm.λn.m + n 0 und sumA = λn.n ∗ (n + 1)/2. Nach Satz 17.1 ist A0 die einzige induktive L¨osung von E in A. o Beispiel 17.3 (CΣ = List(X); siehe 2.1) Seien X eine Menge mit Halbordnung ≤: X × X → 2, DΣ = ({list}, {X, 2}, {≤: X × X → 2, ∗ : 2 × 2 → 2}, {sorted : list → 2, sorted0 : list → 2X }), Ψ = (List(X), DΣ) und x, y, s Variablen. Dann bilden die Gleichungen sorted(nil) sorted(cons(x, s)) sorted0(nil) sorted0(cons(x, s)) = = = = 1, sorted0(s)(x), λx.1, λy.(y ≤ x) ∗ sorted0(s)(x) ein rekursives Ψ-Gleichungssystem E, das in der initialen List(X)-Algebra A mit Alist = X ∗, nilA = , und consA = λ(x, w).xw die induktive L¨osung A0 hat mit 0 sortedA (w) = 1 ⇔ w ist bzgl. ≤ sortiert, 0 sorted0A (w)(x) = 1 ⇔ xw ist bzgl. ≤ sortiert. 339 Nach Satz 17.1 ist A0 die einzige induktive L¨osung von E in A. Beispiel 17.4 (CΣ = Reg(CS); siehe 2.1 und 2.2) Sei X = o S CS, DΣ = ({reg}, {2, X}, {max, ∗ : 2 × 2 → 2} ∪ { ∈ C : X → 2 | C ∈ CS}, {δ : reg → reg X , β : reg → 2}) und Ψ = (Reg(CS), DΣ). Dann bildet die Menge BRE der Brzozowski-Gleichungen von Abschnitt 3.11 ein rekursives Ψ-Gleichungssystem, das in der initialen Reg(CS)-Algebra TReg(CS) die in Abschnitt 2.7 durch Bro(CS) definierte induktive Lo¨sung A hat. Nach Satz 17.1 ist A die einzige induktive L¨osung von BRE in TReg(CS). o Weitere Induktionsverfahren, mit denen Eigenschaften kleinster Relationen bewiesen werden und die daher nicht nur in initialen Modellen anwendbar sind, werden in meinen LVs u¨ber Logisch-Algebraischen Systementwurf behandelt. Coinduktion Sei CΣ = (S, BS, BF, D) eine destruktive Signatur. Die zentrale Methode zum Beweis von Eigenschaften der finalen DΣ-Algebra - unabh¨angig von deren konkreter Repr¨asentation – ist die Coinduktion. Ihre Korrektheit folgt aus Satz 3.4 (3): 340 Sei A eine finale DΣ-Algebra. Die Diagonale von A ist die einzige Kongruenz auf A. (1) Sei E eine Menge von Σ-Gleichungen und A zu einer Σ-Algebra erweiterbar. Wegen (1) gilt E in A, wenn RE = {(g ∗(t), g ∗(u)) ∈ A2 | t = u ∈ E, g : V → A} in einer DΣ-Kongruenz R enthalten ist (siehe Term¨aquivalenz und Normalformen). Coinduktion heißt demnach: die Gu¨ltigkeit von E in A aus der Existenz einer DΣKongruenz schließen, die RE enth¨alt. Beispiel 17.5 Die finale Stream(X)-Algebra coTStream(X) (siehe Beispiel 10.3) ist isomorph zur Stream(X)-Algebra A = X N der Funktionen von N nach X, die die Destruktoren von Stream(X) (siehe 2.2) wie folgt interpretiert: Fu¨r alle f ∈ A, headA(f ) = f (0) und tailA(f ) = λn.f (n + 1). Daru¨berhinaus werden die Konstruktoren evens : list → list und zip : list × list → list wie folgt interpretiert: 341 Fu¨r alle f ∈ A und n ∈ N, f (2n) falls n gerade ist, f (2n + 1) sonst, f (n/2) falls n gerade ist, zipA(f, g)(n) = g(n/2 + 1) sonst. evensA(f )(n) = Dann erfu¨llt A die Gleichungen head(evens(s)) tail(evens(s)) head(zip(s, s0)) tail(zip(s, s0)) = = = = head(s) evens(tail(tail(s))) head(s) zip(s0, tail(s)) (1) (2) (3) (4) mit s, s0 ∈ Vlist. Die Gu¨ltigkeit der Gleichung evens(zip(s, s0)) = s (5) in A soll allein unter Verwendung von (1)-(4) gezeigt werden. Gem¨aß Coinduktion gilt (5) in A, falls RE = {(evensA(zipA(f, g)), f ) | f, g ∈ A} eine Stream(X)-Kongruenz ist, d.h. folgende Eigenschaft hat: Fu¨r alle (f, g) ∈ RE gilt: headA(f ) = headA(g) ∧ (tailA(f ), tailA(g)) ∈ R. (6) 342 Beweis von (6). Sei (f, g) ∈ RE . Dann gibt es h ∈ A mit f = evensA(zipA(g, h)). Daraus folgt (1) (3) headA(f ) = headA(evensA(zipA(g, h))) = headA(zipA(g, h)) = headA(g), (2) tailA(f ) = tailA(evensA(zipA(g, h))) = evensA(tailA(tailA(zipA(g, h)))) (4) (4) = evensA(tailA(zipA(h, tail(g)))) = evensA(zipA(tailA(g), tailA(h))). Daraus folgt (tailA(f ), tailA(g)) = (evensA(zipA(tailA(g), tailA(h))), tailA(g)) ∈ RE . o Sei A eine (CΣ ∪ DΣ)-Algebra und R ⊆ A2. Der C-Abschluss RC von R ist die kleinste ¨ Aquivalenzrelation auf A, die R enth¨alt und fu¨r alle c : e → e0 ∈ C und a, b ∈ Ae folgende Bedingung erfu¨llt: (a, b) ∈ ReC ⇒ (cA(a), cA(b)) ∈ ReC0 . R ist eine DΣ-Kongruenz modulo C, wenn fu¨r alle d : s → e ∈ D und a, b ∈ Ae gilt: (a, b) ∈ Rs ⇒ (dA(a), dA(b)) ∈ ReC . Ist A final und gibt es ein rekursives Ψ-Gleichungssystem, dann ist der C-Abschluss einer DΣ-Kongruenz modulo C auf A nach Satz 17.7 (11) eine DΣ-Kongruenz. Deshalb folgt die Gu¨ltigkeit von E in A bereits aus der Existenz einer DΣ-Kongruenz modulo C, die RE enth¨alt. 343 Coinduktion modulo C heißt demnach: die Gu¨ltigkeit von E in A aus der Existenz einer DΣ-Kongruenz modulo C schließen, die RE enth¨alt. Beispiel 17.6 S Sei X = CS und A die (Reg(CS) ∪ Acc(X))-Algebra mit A|Reg(CS) = Lang(X) und A|Acc(X) = Pow (X) (siehe 2.7). A erfu¨llt die Gleichungen δ(par(t, u)) δ(seq(t, u)) β(par(t, u)) β(seq(t, u)) par(par(t1, u1), par(t2, u2)) par(t, mt) = = = = = = λx.par(δ(t)(x), δ(u)(x)) λx.par(seq(δ(t)(x), u), ite(β(t), δ(u)(x), mt)) max{β(t), β(u)} β(t) ∗ β(u) par(par(t1, t2), par(u1, u2)) t (2) (3) (4) (5) (6) (7) mit t, u, v ∈ Vreg und x ∈ VX . Die Gu¨ltigkeit des Distributivgesetzes seq(t, par(u, v)) = par(seq(t, u), seq(t, v)) (1) in A soll allein unter Verwendung von (1)-(7) gezeigt werden. Gem¨aß Coinduktion modulo C = {par} gilt (1) in A, falls RE = {(seq A(f, parA(g, h)), parA(seq A(f, g), seq A(f, h))) | f, g, h ∈ Lang(X)} eine Acc(X)-Kongruenz modulo C ist, d.h. folgende Eigenschaft hat: 344 Fu¨r alle (f, g) ∈ RE gilt: β A(f ) = β A(g) ∧ (δ A(f ), δ A(g)) ∈ REC . (8) Beweis von (8). Sei (f, g) ∈ RE und x ∈ X. Dann gibt es f 0, g 0, h ∈ A mit f = seq A(f 0, parA(g 0, h)) und g = parA(seq A(f 0, g 0), seq A(f 0, h))). Daraus folgt (5) β A(f ) = β A(seq A(f 0, parA(g 0, h))) = β A(f 0) ∗ β A(parA(g 0, h)) (4) = β A(f 0) ∗ max{β A(g 0), β A(h)} = max{β A(f 0) ∗ β A(g 0), β A(f 0) ∗ β A(h))} (5) = max{β A(seq A(f 0, g 0)), β A(seq A(f 0, h))} (4) = β A(parA(seq A(f 0, g 0), seq A(f 0, h))) = β A(g), δ A(f )(x) = δ A(seq A(f 0, parA(g 0, h))) (3) = parA(seq A(δ A(f 0)(x), parA(g 0, h)), if β A(f 0) = 1 then δ A(parA(g 0, h))(x) else mtA) (2) = parA(seq A(δ A(f 0)(x), parA(g 0, h)), A 0 A A 0 A A if β (f ) = 1 then par (δ (g )(x) else δ (h)(x), mt )), parA(seq A(δ A(f 0)(x), parA(g 0, h)), (7) = parA(δ A(g 0)(x), δ A(h)(x))) falls β A(f 0) = 1 seq A(δ A(f 0)(x), parA(g 0, h)) sonst, 345 δ A(g)(x) = δ A(parA(seq A(f 0, g 0), seq A(f 0, h))))(x) (2) = parA(δ A(seq A(f 0, g 0))(x), δ A(seq A(f 0, h))(x)) (3) = parA(parA(seq A(δ A(f 0)(x), g 0), if β A(f 0) = 1 then δ A(g 0)(x) else mtA), parA(seq A(δ A(f 0)(x), h), if β A(f 0) = 1 then δ A(h)(x) else mtA)) A A A A 0 0 A 0 par (par (seq (δ (f )(x), g ), δ (g )(x)), parA(seq A(δ A(f 0)(x), h), δ A(h)(x))) falls β A(f 0) = 1 = parA(parA(seq A(δ A(f 0)(x), g 0), mtA), parA(seq A(δ A(f 0)(x), h), mtA)) sonst parA(parA(seq A(δ A(f 0)(x), g 0), seq A(δ A(f 0)(x), h)), (6),(7) = parA(δ A(g 0)(x), δ A(h)(x))) falls β A(f 0) = 1 parA(seq A(δ A(f 0)(x), g 0), seq A(δ A(f 0)(x), h)) sonst. Sei f1 = seq A(δ A(f 0)(x), parA(g 0, h)), f2 = parA(seq A(δ A(f 0)(x), g 0), seq A(δ A(f 0)(x), h)), f3 = parA(δ A(g 0)(x), δ A(h)(x)). 346 Wegen (f1, f2) ∈ RE gilt also (δ A(f )(x), δ A(g)(x)) = (parA(f1, f3), parA(f2, f3)) ∈ REC im Fall β A(f 0) = 1 und (δ A(f )(x), δ A(g)(x)) = (f1, f2) ∈ REC im Fall β A(f 0) = 0. o Weitere Coinduktionsverfahren, mit denen Eigenschaften gr¨oßter Relationen bewiesen werden und die daher nicht nur in finalen Modellen anwendbar sind, werden in meinen LVs u¨ber Logisch-Algebraischen Systementwurf behandelt. Coinduktive L¨ osungen Sei E ein rekursives Ψ-Gleichungssystem und A eine DΣ-Algebra. Eine coinduktive L¨ osung von E in A ist eine (CΣ ∪ DΣ)-Algebra, deren DΣ-Redukt mit A u¨bereinstimmt und die E erfu¨llt. 347 Satz 17.7 Sei A eine finale DΣ-Algebra. Dann hat E genau eine coinduktive Lo¨sung in A und TCΣ ist eine DΣ-Algebra mit unfold TCΣ = fold A. Beweis. Sei CΣ(A) = (S, BS, C ∪ A) und V eine (S ∪ BS)-sortierte Variablenmenge, die A enth¨alt! Wir erweitern die CΣ(A)-Algebra TCΣ(A) wie folgt zur Σ-Algebra: Fu¨r alle Operationen f : e → e0 von Σ und t ∈ TCΣ(A),e, f TCΣ(A) (t) = eval(f t), wobei eval : TΣ(V ) → TCΣ(V ) wie unten definiert ist. Die fu¨r eine induktive Definition erforderliche wohlfundierte Ordnung auf den Argumenttermen von eval lautet wie folgt: Fu¨r alle t, t0 ∈ TΣ(V ), t t0 ⇔def (depC2 (t), depD (t), size(t)) >lex (depC2 (t0), depD (t0), size(t0)). >lex ⊆ N3 ×N3 bezeichnet die lexikographische Erweiterung von >⊆ N×N auf Zahlentripel. Sei G ⊆ F . depG(t) bezeichnet die maximale Schachtelungstiefe (= Anzahl von G-Symbolen auf einem Pfad) von t. size(t) bezeichnet die Anzahl der Symbole von t. Die induktive Definition von eval lautet wie folgt: • Fu¨r alle x ∈ V , eval(x) = x. • Fu¨r alle f : e → e0 ∈ B ∪ D und a ∈ Ae, eval(f a) = f A(a). • Fu¨r alle x ∈ V und t ∈ TΣ(V ), eval(λx.t) = λx.eval(t). (1) 348 Fu¨r alle c : hein → s ∈ C und ti ∈ TΣ(V )ei , 1 ≤ i ≤ n, eval(c(t1, . . . , tn)) = c(eval(t1), . . . , eval(tn)). (2) Fu¨r alle t, u ∈ TΣ(V ), eval(t(u)) = eval(t)(eval(u)). Fu¨r alle t, u, v ∈ TΣ(V ), eval(ite(t, u, v)) = ite(eval(t), eval(u), eval(v)). • Fu¨r alle d : s → e0 ∈ D, c : e → s ∈ C und (t1, . . . , tn) ∈ TΣ(V )e, eval(dc(t1, . . . , tn)) = u{t1/x1, . . . , tn/xn, eval(u1σ)/z1, . . . , eval(uk σ)/zk }, (3) wobei u ∈ TCΣ(V ), {z1, . . . , zk } = var(u) \ {x1, . . . , xn}, u1, . . . , uk ∈ TΣ(V ) aus Destruktoren und Variablen bestehen, σ = {t1/x1, . . . , tn/xn} und td,c = u{u1/z1, . . . , uk /zk }. • Fu¨r alle d : s → e ∈ D, d0 : s0 → s ∈ D und u ∈ TΣ(V )s0 , eval(dd0u) = eval(d eval(d0u)). (4) (5) Fu¨r alle t ∈ TΣ(V ) ist eval(t) definiert und depC2 (eval(t)) ≤ depC2 (t). Beweis. Wir zeigen (5) durch Induktion u¨ber t entlang . Fall (1): Es gibt f : e → e0 ∈ B ∪ D und a ∈ Ae mit t = f a. eval(t) = f A(a) ist definiert und depC2 (eval(t)) = 0 = depC2 (t). Fall (2): Es gibt c : e → s ∈ C ∪ {λx. | x ∈ V } ∪ { ( ), ite} und (t1, . . . , tn) ∈ TΣ(v)e mit t = c(t1, . . . , tn). Sei 1 ≤ i ≤ n. 349 Ist c ∈ C2, dann gilt depC2 (ti) < depC2 (t). Ist c 6∈ C2, dann gilt depG(eval(ti)) ≤ depG(t) fu¨r G ∈ {C2, D}, aber size(ti) < size(t). Demnach gilt t ti in beiden Unterf¨allen. Also ist nach Induktionsvoraussetzung eval(ti) definiert und depC2 (eval(ti)) ≤ depC2 (ti). Daraus folgt, dass auch eval(t) = c(eval(t1), . . . , eval(tn)) definiert ist und depC2 (eval(t)) = max{depC2 (eval(ti)) | 1 ≤ i ≤ n} ≤ max{depC2 (ti) | 1 ≤ i ≤ n} = depC2 (t) im Fall c ∈ C1 bzw. depC2 (eval(t)) = 1 + max{depC2 (eval(ti)) | 1 ≤ i ≤ n} ≤ 1 + max{depC2 (ti) | 1 ≤ i ≤ n} = depC2 (t) im Fall c ∈ C2. Fall (3): Es gibt d : s → e0 ∈ D, c : e → s ∈ C und (t1, . . . , tn) ∈ TΣ(V )e mit t = dc(t1, . . . , tn). Seien k, u, σ, z1, . . . , zk , u1, . . . , uk wie oben und 1 ≤ i ≤ k. Ist c ∈ C1, dann ist uiσ ein echter Teilterm von t und damit depG(uiσ) ≤ depG(t) fu¨r G ∈ {C2, D}, aber size(uiσ) < size(t). Ist c ∈ C2, dann gilt depC2 (uiσ) < depC2 (t). Folglich gilt t uiσ in beiden Unterf¨allen. Also ist nach Induktionsvoraussetzung eval(uiσ) definiert und depC2 (eval(uiσ)) ≤ depC2 (uiσ). Demnach ist auch eval(t) = u{t1/x1, . . . , tn/xn, eval(u1σ)/z1, . . . , eval(uk σ)/zk } 350 definiert und depC2 (eval(t)) ≤ depC2 (t), weil jeder Pfad von u im Fall c ∈ C1 kein C2Symbol und im Fall c ∈ C2 h¨ochstens eins enth¨alt. Fall (4): Es gibt d : s → e ∈ D, d0 : s0 → s ∈ D und u ∈ TΣ(V )s0 mit t = dd0u. Dann gilt depC2 (d0u) ≤ depC2 (t), aber depD (d0u) ≤ depD (t), also t d0u. Damit ist nach Induktionsvoraussetzung eval(d0u) definiert und depC2 (eval(d0u)) ≤ depC2 (d0u), also auch depC2 (d eval(d0u)) = depC2 (eval(d0u)) ≤ depC2 (t). Wegen eval(d0u) ∈ TCΣ(V ) ist jedoch depD (d eval(d0u)) < depD (t), so dass nach Induktionsvoraussetzung auch eval(t) = eval(d eval(d0u)) definiert ist und depC2 (eval(d eval(d0u))) ≤ depC2 (d eval(d0u)). Daraus folgt schließlich depC2 (eval(t)) ≤ depC2 (d eval(d0u)) ≤ depC2 (t). o Wie man ebenfalls durch Induktion u¨ber t entlang zeigen kann, kommen alle Variablen von eval(t) in t vor. Daraus folgt f T (t) = eval(f t) ∈ TCΣ(A) und f T (u) = eval(f u) ∈ TCΣ fu¨r alle Operationen f : e → e0 von Σ, t ∈ TCΣ(A),e und u ∈ TCΣ. Letzteres macht TCΣ zur DΣ-Unteralgebra von TCΣ(A). (6) Fu¨r alle c : e → s ∈ C und (t1, . . . , tn) ∈ TCΣ(A),e, cT (t1, . . . , tn) = c(t1, . . . , tn). Beweis. eval(t) = t fu¨r alle t ∈ TCΣ(A) erha¨lt man durch Induktion u¨ber die Gro¨ße von t. Daraus folgt cT (t1, . . . , tn) Def . cT = (2) eval(c(t1, . . . , tn)) = c(eval(t1), . . . , eval(tn)) eval(ti )=ti = c(t1, . . . , tn). 351 (7) Fu¨r alle g : V → TCΣ(A) und Σ-Terme t, die aus Destruktoren und einer Variable x bestehen, gilt eval(tσ) = g ∗(t), wobei σ = {g(x)/x}. Beweis durch Induktion u ¨ber die Anzahl der Destruktoren von t. Sei d1, . . . , dn ∈ D und t = d1 . . . dnx. Ist n = 0, dann gilt eval(tσ) = eval(xσ) = eval(g(x)) g(x)∈TCΣ(A) = g(x) = g ∗(x) = g ∗(t). Andernfalls ist (4) eval(tσ) = eval(d1 . . . dnxσ) = eval(d1 eval(d2 . . . dnxσ)) Induktionsvor . = Def . g ∗ = ∗ eval(d1 g (d2 . . . dnx)) Def . dT1 = dT1 (g ∗(d2 . . . dnx)) g ∗(d1 . . . dnx) = g ∗(t). o (8) TCΣ(A) erfu¨llt E. Beweis. Fu¨r alle c : s1 × · · · × sn → s ∈ C, d : s → e ∈ D und σ = g : V → TCΣ(A), g ∗(dc(x1, . . . , xn)) Def . dT = Def . g ∗ = dT (cT (g(x1), . . . , g(xn))) (6) eval(dcT (g(x1), . . . , g(xn))) = eval(dc(g(x1), . . . , g(xn))) (3) = u{x1σ/x1, . . . , xnσ/xn, eval(u1σ)/z1, . . . , eval(uk σ)/zk } (7) = u{x1σ/x1, . . . , xnσ/xn, g ∗(u1)/z1, . . . , g ∗(uk )/zk } u∈TCΣ (V ) = g ∗(td,c). o 352 Da TCΣ(A) eine DΣ-Algebra und A die finale DΣ-Algebra ist, gibt es den eindeutigen DΣHomomorphismus unfold T : TCΣ(A) → A. (9) Fu¨r alle a ∈ A, unfold T (a) = a. Beweis. Fu¨r alle d : s → e ∈ D gilt dA(a) = dT (a). Folglich sind die Inklusion incA : A → TCΣ(A) und daher auch die Komposition unfold T ◦ incA : A → A DΣ-homomorph. Also stimmt diese wegen der Finalit¨at von A mit der Identit¨at auf A u¨berein. o A ist eine CΣ-Algebra: Fu¨r alle c : e → s ∈ C und a ∈ Ae, cA(a) =def unfold T (c(a)). (10) A erfu¨llt E. Beweis. Fu¨r alle c : s1 × · · · × sn → s ∈ C, d : s → e ∈ D und g : V → A, g ∗(dc(x1, . . . , xn)) = dA(cA(g(x1), . . . , g(xn))) (10) = dA(unfold T (c(g(x1), . . . , g(xn)))) unfold T DΣ−homomorph = unfold T (dT (c(g(x1), . . . , g(xn)))) (6) = unfold T (dT (cT (g(x1), . . . , g(xn)))) = unfold T (g ∗(dc(x1, . . . , xn))) 353 (8) (9) = unfold T (g ∗(td,c)) = g ∗(td,c). o (10) liefert also eine coinduktive L¨osung von E in A. Sei R die gro¨ßte DΣ-Kongruenz auf TCΣ(A). (11) R ist eine CΣ-Kongruenz. Beweis. Sei RC der C-Abschluss von R (s.o.). Ist RC eine DΣ-Kongruenz, dann ist RC in R enthalten, weil R die gr¨oßte DΣ-Kongruenz ist. Andererseits ist R in RC enthalten. Also stimmt R mit RC u¨berein, ist also wie RC eine CΣ-Kongruenz. Demnach bleibt zu zeigen, dass RC eine DΣ-Kongruenz ist. Sei also d : s → e ∈ D und (t, u) ∈ RsC . Geho¨rt (t, u) zu R, dann gilt das auch fu¨r (dT (t), dT (u)), weil R eine DΣ-Kongruenz ist. Wegen R ⊆ RC folgt (dT (t), dT (u)) ∈ ReC . Andernfalls gibt es c : s1 × · · · × sn → s ∈ C und t1, . . . , tn, u1, . . . , un ∈ TCΣ(A) mit t = c(t1, . . . , tn), u = c(u1, . . . , un) und (ti, ui) ∈ RC fu¨r alle 1 ≤ i ≤ n. Nach Induktionsvoraussetzung gilt (d0T (ti), d0T (ui)) ∈ ReC0 fu¨r alle 1 ≤ i ≤ n und d0 : si → e0 ∈ D. Seien g, g 0 Belegungen von V in TCΣ(A) mit g(xi) = ti und g 0(xi) = ui fu¨r alle 1 ≤ i ≤ n. 354 Wegen (6) (8) dT (t) = dT (c(t1, . . . , tn)) = dT (cT (t1, . . . , tn)) = g ∗(td,c), (6) (8) dT (u) = dT (c(u1, . . . , un) = dT (cT (u1, . . . , un)) = g 0∗(td,c) und weil RC eine CΣ-Kongruenz auf TCΣ(A) ist, folgt (dT (t), dT (u)) ∈ ReC aus (g(xi), g 0(xi)) ∈ RC fu¨r alle 1 ≤ i ≤ n. Also ist RC eine DΣ-Kongruenz. o (11) liefert folgende CΣ-Algebra B mit den Tra¨germengen von A: Fu¨r alle c : e → s ∈ C und t ∈ TCΣ(A),e, cB (unfold T (t)) =def unfold T (c(t)). (12) cB ist wohldefiniert: Sei t, u ∈ TCΣ(A),e mit unfold T (t) = unfold T (u). Da A final ist, stimmt R nach Satz 3.4 (3) mit dem Kern von unfold T u¨berein. Also impliziert (11), dass ker(unfold T ) eine CΣ-Kongruenz auf TCΣ(A) ist. Daraus folgt (12) (6) (6) cB (unfold T (t)) = unfold T (c(t)) = unfold T (cT (t)) = unfold T (cT (u)) = unfold T (c(u)) (12) = cB (unfold T (u)). (13) cB stimmt mit cA u¨berein: Fu¨r alle a ∈ A, (9) (12) (10) cB (a) = cB (unfold T (a)) = unfold T (c(a)) = cA(a). (14) unfold T ist CΣ-homomorph: Fu¨r alle c : e → s ∈ C und t ∈ TCΣ(A),e, (6) (12) (13) unfold T (cT (t)) = unfold T (c(t)) = cB (unfold T (t)) = cA(unfold T (t)). 355 (15) unfold TCΣ = fold A: Da TCΣ eine DΣ-Unteralgebra von TCΣ(A) ist und deshalb genau ein DΣ-Homomorphismus von TCΣ nach A existiert, stimmt unfold TCΣ mit der Einschr¨ankung von unfold TCΣ(A) auf TCΣ u¨berein. Da incTCΣ : TCΣ → TCΣ(A) und – wegen (14) – unfold TCΣ(A) CΣ-homomorph sind, folgt (15) aus der Initialit¨at von TCΣ. unfold TCΣ TCΣ id g TCΣ incTCΣ TCΣ(A) (15) unfold TCΣ(A) fold A A id g A Es bleibt zu zeigen, dass je zwei coinduktive L¨osungen A1, A2 von E in A miteinander u¨bereinstimmen. Sei Q die kleinste S-sortige Relation auf A1 × A2, die die Diagonale von A enth¨alt und fu¨r alle c : e → s ∈ C und a, b ∈ Ae die folgende Implikation erfu¨llt: (a, b) ∈ Q ⇒ (cA1 (a), cA2 (b)) ∈ Q. (15) 356 (16) Q ist eine DΣ-Kongruenz. Beweis. Sei d : s → e ∈ D und (a, b) ∈ Qs. Geh¨ort (a, b) zu ∆A, dann gilt a = b, also dA(a) = dA(b). Daraus folgt (dA(a), dA(b)) ∈ Qe, weil Q die Diagonale von A enth¨alt. Andernfalls gibt es c : s1 × · · · × sn → s ∈ C und a1, . . . , an, b1, . . . , bn ∈ A mit a = cA1 (a1, . . . , an), b = cA2 (b1, . . . , bn) und (ai, bi) ∈ Q fu¨r alle 1 ≤ i ≤ n. Nach Induktionsvoraussetzung gilt (d0A(ai), d0A(bi)) ∈ Qe0 fu¨r alle 1 ≤ i ≤ n und d0 : si → e0 ∈ D. Seien g, g 0 : V → A Belegungen mit g(xi) = ai und g 0(xi) = bi fu¨r alle 1 ≤ i ≤ n. Wegen dA(a) = dA(cA1 (a1, . . . , an)) = g ∗(td,c), dA(b) = dA(cA2 (b1, . . . , bn) = g 0∗(td,c) und weil Q ein CΣ-Kongruenz ist, folgt (dA(a), dA(b)) ∈ Qe aus (g(xi), g 0(xi)) ∈ Q fu¨r alle 1 ≤ i ≤ n. o Wegen der Finalit¨at von A ist nach Satz 3.4 (3) die Diagonale von A die einzige DΣKongruenz auf A. Also impliziert (16), dass Q mit ∆A u¨bereinstimmt. Sei c : e → s ∈ C und a ∈ Ae. Aus (a, a) ∈ Q und (15) folgt (cA1 (a), cA2 (a)) ∈ Qe, also cA1 (a) = cA2 (a) wegen Q = ∆A. Demnach gilt A1 = A2. o 357 Beispiel 17.8 (DΣ = Stream(X); siehe 2.2) Sei CΣ = ({list}, ∅, ∅, {evens, odds, exchange, exchange0 : list → list}), Ψ = (CΣ, Stream(X)) und s eine Variable. Dann bilden die Gleichungen head(evens(s)) = head(s), tail(evens(s)) = evens(tail(tail(s))), head(odds(s)) = head(tail(s)), tail(odds(s)) = odds(tail(tail(s))), head(exchange(s)) = head(tail(s)), tail(exchange(s)) = exchange0(s), head(exchange0(s)) = head(s), tail(exchange(s)) = exchange(tail(tail(s))) ein rekursives Ψ-Gleichungssystem E, das nach Satz 17.7 genau eine coinduktive L¨osung in der finalen Stream(X)-Algebra X N hat. evens(s) und odds(s) listen die Elemente von s auf, die dort an geraden bzw. ungeraden Positionen stehen. exchange(s) vertauscht die Elemente an geraden mit denen an ungeraden Positionen. Satz 17.1 ist auf E nicht anwendbar, da hier die Menge C2 der Konstruktoren, in deren Gleichungen Destruktoren geschachtelt auftreten, nicht leer ist. o Beispiel 17.9 Sei Ψ die Bisignatur von Beispiel 17.4 und BRE das rekursive Ψ-Gleichungssystem von Abschnitt 3.11. BRE hat in der finalen Acc(X)-Algebra Pow (X) bzw. χ(Pow (X)) = Beh(X, 2) die in Abschnitt 2.7 durch Lang(X) bzw. regBeh definierte coinduktive L¨osung A bzw. B. 358 Nach Satz 17.7 ist A bzw. B die einzige coinduktive L¨osung von BRE in Pow (X) bzw. Beh(X, 2). Daru¨berhinaus folgt fold Lang(X) = unfold Bro(CS) : Bro(CS) → Pow (X) und fold regBeh = unfold 0Bro(CS) : Bro(CS) → Beh(X, 2) aus Satz 17.7 und damit die Acc(X)-Homomorphie beider Faltungen. Wegen der Eindeutigkeit der zweiten Entfaltung und der Acc(X)-Homomorphie der in Abschnitt 2.7 definierten ∗ Funktion χ : P(X ∗) → 2X folgt fold regBeh = unfold 0Bro(CS) = χ ◦ unfold Bro(CS) = χ ◦ fold Lang(X). In Kapitel 2 wurde diese Gleichung aus der Reg(CS)-Homomorphie von χ geschlossen. Mit Satz 17.7 erhalten wir sie direkt. o Beispiel 17.10 (siehe 2.2) Sei A eine DAut(X, Y )-Algebra, S =def Astate, CΣ = ({state}, {S, X, 2}, {δ A : S → S X , β A : S → Y }, {beh : S → state}), Ψ = (CΣ, DAut(X, Y )) und x eine Variable vom Typ X. E = {δ(beh(s)) = λx.beh(δ A(s)(x)) | s ∈ S} ∪ {β(beh(s)) = β A(s) | s ∈ S} 359 ist ein rekursives Ψ-Gleichungssystem, das nach Satz 17.7 genau eine coinduktive Lo¨sung B in der finalen DAut(X, Y )-Algebra Beh(X, Y ) (und Pow (X) im Fall Y = 2) hat. B interpretiert die Konstruktoren von CΣ wie folgt: Fu¨r alle s ∈ S, sB = unfold A(s). Wegen der Eindeutigkeit von B realisiert (bzw. akzeptiert) fu¨r alle s ∈ S der initiale Automat (A, s) eine gegebene Verhaltensfunktion fs : X ∗ → Y (bzw. Sprache Ls ⊆ X ∗) genau dann, wenn die (CΣ, DAut(X, Y ))-Algebra B mit B|DAut(X,Y ) = Beh(X, Y ) (bzw. B|Acc(X) = Pow (X)) und behB (s) = fs (bzw. behB (s) = Ls) fu¨r alle s ∈ S E erfu¨llt. Sei z.B. A die Acc(Z)-Algebra eo von Beispiel 11.4. Dann besteht E aus den Gleichungen δ(beh(Esum)) δ(beh(Osum)) β(beh(Esum)) β(beh(Osum)) = = = = λx.beh(if even(x) then Esum else Osum), λx.beh(if even(x) then Osum else Esum), 1, 0. (1) (A, Esum) und (A, Osum) akzeptieren die Sprachen Pn L = {(x1, . . . , xn) ∈ Z∗ | i=1 xi ist gerade} ∪ {} bzw. P n L0 = {(x1, . . . , xn) ∈ Z∗ | i=1 xi ist ungerade}, weil die (CΣ ∪ Acc(Z))-Algebra B mit B|Acc(Z) = Pow (Z), behB (Esum) = L und behB (Osum) = L0 E erfu¨llt. Laut Abschnitt 2.11 folgt (1) auch aus der Tatsache, dass die Funktion h : A → Pow (X) mit h(Esum) = L und h(Osum) = L0 360 ein Acc(Z)-Homomorphismus ist, weil dann wegen der Finalit¨at von Pow (X) (Satz 2.10) h mit unfold A u¨bereinstimmt (siehe [29], Example ESUM). o Entscheidende Argumente im Beweis von Satz 17.7 entstammen dem Beweis von [34], Thm. 3.1, und [35], Thm. A.1, wo rekursive Stream(X)-Gleichungen fu¨r Stromkonstruktoren untersucht werden (siehe auch [7], Anh¨ange A.5 and A.6). Zur Zeit werden Satz 17.7 ¨ahnliche Theoreme entwickelt und auf weitere destruktive Signaturen – neben Stream(X) und Acc(X) – angewendet, z.B. auf unendliche Bin¨arb¨aume ([36], Thm. 2), nichtdeterministische Systeme [1], Mealy-Automaten [6], nebenl¨aufige Prozesse [10, 32, 11] und formale Potenzreihen ([34], Kapitel 9). Hierbei wird oft von der traditionellen Darstellung rekursiver Gleichungssysteme als strukturell-operationelle Semantikregeln (SOS) ausgegangen, wobei “strukturell” und “operationell” fu¨r die jeweiligen Konstruktoren bzw. Destruktoren steht. Z.B. lauten die Gleichungen von BRE als SOS-Regeln wie folgt: δ eps −→ λx.mt δ mt −→ λx.mt δ C −→ λx.ite(x ∈ C, eps, mt) 361 δ δ δ δ t −→ t0, u −→ u0 t −→ t0, u −→ u0 δ δ seq(t, u) −→ λx.par(seq(t0(x), u), ite(β(t), u0(x), mt)) par(t, u) −→ λx.par(t0(x), u0(x)) δ t −→ t0 δ iter(t) −→ λx.seq(t0(x), iter(t)) β β eps −→ 1 mt −→ 0 β β t −→ m, u −→ n β par(t, u) −→ max{m, n} β C −→ 0 β β t −→ m, u −→ n β seq(t, u) −→ m ∗ n β iter(t) −→ 0 Im Gegensatz zu einer operationellen Semantik besteht eine denotationelle Semantik nicht aus Regeln, sondern aus funktionalen Interpretationen von Konstruktoren und Destruktoren, ist also eine Algebra. In der Kategorientheorie werden rekursive Gleichungssysteme und die Beziehungen zwischen ihren Komponenten mit Hilfe von distributive laws und Bialgebren verallgemeinert (siehe z.B. [37, 13, 8, 11]). 362 18 Iterative Gleichungen Sei Σ = (S, BS, BF, F ) eine konstruktive Signatur und V eine endliche (!) S-sortige Menge von Variablen. Eine S-sortige Funktion E : V → TΣ(V ) heißt iteratives Σ-Gleichungssystem, falls das Bild von E keine Variablen enth¨alt. Demnach sind iterative Gleichungssysteme spezielle Substitutionen (siehe Abschnitt 3.6). Sei A eine Σ-Algebra und AV die Menge der S-sortigen Funktionen von V nach A. g ∈ A V l¨ ost E in A, wenn g ∗ ◦ E = g gilt. Lemma 18.1 Sei E 0 eine Menge von Σ-Gleichungen, A ∈ AlgΣ,E 0 und reduce : TΣ(V ) → TΣ(V ) eine Funktion, die jeden Σ-Term auf einen E 0-¨aquivalenten Term abbildet (siehe Kapitel 3). Fu¨r alle L¨osungen g : V → A von E in A und n ∈ N gilt g ∗ = g ∗ ◦ (reduce ◦ E ∗)n. Beweis durch Induktion u ¨ber n. g ∗ = g ∗ ◦ idTΣ(V ) = g ∗ ◦ (E ∗)0. 363 ¨ Da ≡E 0 der Aquivalenzabschluss der kleinste Σ-Kongruenz auf TΣ(V ) ist, die E 0 enth¨alt und unter Instanziierung abgeschlossen ist, ist ≡E 0 im Kern von g ∗ enthalten. Daraus folgt nach Voraussetzung g ∗ ◦ reduce = g ∗, (1) also g∗ Induktionsvor . = g ∗ ◦ (reduce ◦ E ∗)n 3.3(1) g l¨ ost E in A = (g ∗ ◦ E)∗ ◦ (reduce ◦ E ∗)n (1) = g ∗ ◦ E ∗ ◦ (reduce ◦ E ∗)n = g ∗ ◦ reduce ◦ E ∗ ◦ (reduce ◦ E ∗)n = g ∗ ◦ (reduce ◦ E ∗)n+1. o 18.2 Das iterative Gleichungssystem einer CFG Sei G = (S, BS, Z, R) eine CFG, CS = BS ∪ Z und X = Z ∪ S BS. Im Folgenden betrachten wir die Konstruktoren par and seq von Reg(CS) als Operationen ver¨anderlicher Stelligkeit und schreiben daher • par(t1, . . . , tn) anstelle von par(t1, par(t2, . . . , par(tn−1, tn) . . . )) und • seq(t1, . . . , tn) anstelle von seq(t1, seq(t2, . . . , seq(tn−1, tn) . . . )). par(t) und seq(t) stehen fu¨r t. 364 G induziert ein iteratives Reg(CS)-Gleichungssystem: EG : S → TReg(CS)(S) s 7→ par(w1, . . . , wk ), wobei {w1, . . . , wk } = {w ∈ (S ∪ CS)∗ | s → w ∈ R} und fu¨r alle n > 1, e1, . . . , en ∈ S ∪ CS and s ∈ S, e1 . . . en = seq(e1, . . . , en), s = s. EG heißt Gleichungssystem von G. Satz 18.3 (Fixpunktsatz fu ¨ r CFGs) (i) Die Funktion solG : S → Lang(X) mit solG(s) = L(G)s fu¨r alle s ∈ S l¨ost EG in Lang(X). Sei g : S → P(X ∗) eine L¨osung von EG in Lang(X). (ii) Die S-sortierte Menge Sol mit Sol s = g(s) fu¨r alle s ∈ S ist Tr¨agermenge einer Σ(G)-Unteralgebra von Word (G). (iii) Fu¨r alle s ∈ S, solG(s) ⊆ g(s), m.a.W.: die Sprache von G ist die kleinste L¨osung von EG in Lang(X). 365 Beweis. Sei g : V → Lang(X), s ∈ S, {r1, . . . , rk } die Menge aller Regeln von G mit linker Seite s. Sei 1 ≤ i ≤ k, ri = (s → wi) und dom(fri ) = ei1 × . . . × eini . Dann gibt es s1, . . . , sn ∈ S ∪ CS mit e1 . . . en = wi und g ∗(wi) = g ∗(e1 . . . en) = g ∗(seq(e1, . . . , en)) = seq Lang (g ∗(e1), . . . , g ∗(en)) Word (G) = g ∗(e1) · · · · · g ∗(en) = fri (g ∗(ei1) · · · · · g ∗(eini )). (1) Beweis von (i). (G) solG(s) = L(G)s = fold Word (TΣ(G),s) s S (G) = ki=1{fold Word (fri (t)) | t ∈ TΣ(G),ei1×...×ein } s i Sk Word (G) Word (G) = i=1{fri (fold ei1×...×ein (t)) | t ∈ TΣ(G),ei1×...×ein } i i Sk Sk Word (G) Word (G) Word (G) = i=1 fri (img(fold ei1×...×ein )) = i=1 fri (L(G)ei1×...×eini ) i S Word (G) = ki=1 fri (L(G)ei1 · · · · · L(G)eini ) S Word (G) ∗ ∗ (eini )) = ki=1 fri (solG (ei1) · · · · · solG (1) Sk ∗ ∗ ∗ = i=1 solG (wi) = parLang (solG (w1), . . . , solG (wk )) 366 ∗ ∗ = solG (par(w1, . . . , wk )) = solG (EG(s)). ∗ Also ist solG = solG ◦ EG und damit eine L¨osung von EG in Lang(X). Beweis von (ii). Zu zeigen: Fu¨r alle 1 ≤ i ≤ k, (G) (Sol ei1 · · · · · Sol eini ) ⊆ Sol s. frWord i (2) Nach Definition von Sol gilt Sol eij = g ∗(eij ) fu¨r alle 1 ≤ j ≤ ni. Beweis von (2). Word (G) Word (G) (Sol ei1 · · · · · Sol eini ) = fri (g ∗(ei1) · · · · · g ∗(eini )) S (1) ∗ = g (wi) ⊆ ki=1 g ∗(wi) = parLang (g ∗(w1), . . . , g ∗(wk )) = g ∗(par(w1, . . . , wk )) fri Def . EG = g ∗(EG(s)) = g(s) = Sol s. Beweis von (iii). Nach Satz 3.2 (3) ist L(G) = img(fold Word (G)) die kleinste Σ(G)Unteralgebra von Word (G). Aus (ii) folgt demnach L(G)s ⊆ Sol s, also solG(s) = L(G)s ⊆ Sol s = g(s) fu¨r alle s ∈ S. o 367 18.4 Beispiele 1. Sei X = {a, b} und G = ({A, B}, ∅, X, {A → BA, A → a, B → b}). EG ist wie folgt definiert: EG(A) = par(seq(A, B), a), EG(B) = b. Die einzige L¨osung g von EG in Lang(X) lautet: g(A) = {b}∗ · {a}, g(B) = {b}. 2. Sei G = SAB (siehe Beispiel 4.5). EG ist wie folgt definiert: EG(S) = par(seq(a, B), seq(b, A), eps) EG(A) = par(seq(a, S), seq(b, A, A)), EG(B) = par(seq(b, S), seq(a, B, B)). Die einzige L¨osung g von EG in Lang(X) lautet: g(S) = {w ∈ {a, b}∗ | #a(w) = #b(w)}, g(A) = {w ∈ {a, b}∗ | #a(w) = #b(w) + 1}, g(B) = {w ∈ {a, b}∗ | #a(w) = #b(w) − 1}. 368 Andererseits ist g mit g(S) = g(A) = g(B) = {a, b}+ keine L¨osung von EG in Lang(X), weil a einerseits zu g(S) geh¨ort, aber nicht zu g ∗(EG(S)) = g ∗(par(seq(a, B), seq(b, A))) = ({a} · g(B)) ∪ ({b} · g(A)). Beide Grammatiken sind nicht linksrekursiv. Deshalb haben ihre Gleichungssysteme jeweils genau eine L¨osung in Lang(X) (siehe Satz 18.7). o 18.5 Erweiterung von Bro(CS) um Erkenner kontextfreier Sprachen Sei G = (S, BS, Z, R) eine nicht-linksrekursive CFG, CS = BS ∪ Z und reduce die in Beispiel 3.10 beschriebene Reduktionsfunktion fu¨r regul¨aren Ausdru¨cke. Fu¨r alle s ∈ S gibt es ks, ns > 0, Cs,1, . . . , Cs,ns ∈ CS und Reg(CS)-Terme ts,1, . . . , ts,ns u¨ber S mit (reduce ◦ EG∗ )ks (s) = par(seq(Cs,1, ts,1), . . . , seq(Cs,ns , ts,ns )) (3) (reduce ◦ EG∗ )ks (s) = par(seq(Cs,1, ts,1), . . . , seq(Cs,ns , ts,ns ), eps). (4) oder Sei Seps die Menge aller Sorten von S, die (4) erfu¨llen, Reg(CS)S die Erweiterung von Reg(CS) um einen Konstruktor s : 1 → reg fu¨r alle s ∈ S, DΣ wie in Beispiel 17.4 definiert und 369 ES = {δ(s) = λx.par(ite(χ(Cs,1)(x), ts,1, mt), . . . , ite(χ(Cs,ns )(x), ts,ns , mt)) | s ∈ S} ∪ {β(s) = 1 | s ∈ Seps} ∪ {β(s) = 0 | s ∈ S \ Seps}. Sei Σ = (S, BS, F, BF ) eine Signatur. Setzt man fu¨r alle s ∈ S jede Variable x ∈ Vs mit der Konstante x : 1 → s gleich erweitert man Σ durch die Hinzunahme dieser Konstanten zu ΣV = (S, BS, BF, F ∪ {x : 1 → s | x ∈ Vs, s ∈ S}), dann stimmen die Tr¨agermengen von TΣ(V ) bzw. TΣV u¨berein, d.h. fu¨r alle s ∈ S gilt TΣ(V )s = TΣV ,s. Lemma 18.6 Fu¨r alle ΣV -Algebren A und die Belegung g : V → A mit g(x) = xA gilt g ∗ = fold A. Beweis. g ∗ ist der einzige Σ-Homorphismus von TΣ(V ) nach A mit g ∗ ◦ incV = g. Wegen xA = g(x) = g ∗(x) fu¨r alle x ∈ V ist g ∗ sogar ΣV -homomorph und stimmt deshalb mit fold A : TΣ(V ) = TΣV → A u¨berein. o 370 Satz 18.7 Sei ΨS = (Reg(CS)S , DΣ), Σ = Reg(CS)S ∪ DΣ, sol : S → Lang(X) eine L¨osung von EG in Lang(X) und A die Σ-Algebra, deren Reg(CS)- and Acc(X)-Redukte von A mit Lang(X) bzw. Pow (X) u¨bereinstimmen und die fu¨r alle s ∈ S s durch sol(s) interpretiert. A ist eine coinduktive L¨osung des rekursiven ΨS -Gleichungssystem BRE ∪ ES (siehe 3.11 und oben) in der finalen Acc(X)-Algebra Pow (X). Beweis. Nach Beispiel 17.9 erfu¨llt A die Gleichungen von BRE . Es genu¨gt deshalb zu zeigen, dass auch die Gleichungen von ES in A gelten. Sei g : TΣ(V ) → A. Nach Lemma 18.6 gilt Fu¨r alle t ∈ TReg(CS)S , sol∗ = fold A : TReg(CS)(S) = TReg(CS)S → A. (5) fold A(t) = g ∗(t). (6) Sei s ∈ S. Definitionen und Lemma 18.1 liefern zun¨achst g ∗(s) = sA = sol(s) = sol∗(s) = sol∗((reduce ◦ EG∗ )ks (s)). (7) 371 Geho¨rt s nicht zu Seps, dann gilt sol∗((reduce ◦ EG∗ )ks (s)) = sol∗(par(seq(Cs,1, ts,1), . . . , seq(Cs,ns , ts,ns ))) A A = parA(seq A(Cs,1 , sol∗(ts,1)), . . . , seq A(Cs,ns , sol∗(ts,ns ))) S ns = i=1(Cs,i · sol∗(ts,i)), (8) also (7) g ∗(δ(s)) = δ A(g ∗(s)) = δ A(sol∗((reduce ◦ EG∗ )ks (s))) S (8) A Sns = δ ( i=1(Cs,i · sol∗(ts,i))) = λx.δ A( ni=1(Cs,i · sol∗(ts,i)))(x) Sn Def . δ A ∗ = λx.{w ∈ X | xw ∈ i=1(Cs,i · sol∗(ts,i))} = λx.{w ∈ X ∗ | x ∈ Cs,i, w ∈ sol∗(ts,i), 1 ≤ i ≤ n} S s = λx. ni=1 {w ∈ X ∗ | x ∈ Cs,i, w ∈ sol∗(ts,i)} S s = λx. ni=1 {w ∈ X ∗ | if x ∈ Cs,i then w ∈ sol∗(ts,i) else w ∈ ∅} S s = λx. ni=1 (if x ∈ Cs,i then sol∗(ts,i) else ∅) S s = λx. ni=1 (if x ∈ Cs,i then sol∗(ts,i) else mtA) S s = λx. ni=1 (if x ∈ Cs,i then sol∗(ts,i) else sol∗(mt)) S ns = λx. i=1 sol∗(ite(χ(Cs,i)(x), ts,i, mt)) = λx.sol∗(par(ite(x ∈ Cs,1, ts,1, mt), . . . , ite(x ∈ Cs,ns , ts,ns , mt))) 372 = sol∗(λx.par(ite(χ(Cs,1)(x), ts,1, mt), . . . , ite(χ(Cs,ns )(x), ts,ns , mt))) (5) = fold A(λx.par(ite(Cs,1)(x), ts,1, mt), . . . , ite(χ(Cs,ns )(x), ts,ns , mt))) (6) = g ∗(λx.par(ite(χ(Cs,1)(x), ts,1, mt), . . . , ite(χ(Cs,ns )(x), ts,ns , mt))) und (8) (7) g ∗(β(s)) = β A(g ∗(s)) = β A(sol∗((reduce ◦ EG∗ )ks (s))) = β A( Def . β A = S ns i=1 (Cs,i · sol∗(ts,i))) 0 = g ∗(0). Geh¨ort s zu Seps, dann gilt sol∗((reduce ◦ EG∗ )ks (s)) = sol∗(par(seq(Cs,1, ts,1), . . . , seq(Cs,ns , ts,ns ), eps)) A A = parA(seq A(Cs,1 , sol∗(ts,1)), . . . , seq A(Cs,ns , sol∗(ts,ns ), epsA)) S s = ni=1 (Cs,i · sol∗(ts,i)) ∪ {}, (9) also (7) g ∗(δ(s)) = δ A(g ∗(s)) = δ A(sol∗((reduce ◦ EG∗ )ks (s))) S s (9) A Sns (Cs,i · sol∗(ts,i)) ∪ {})(x) = δ ( i=1(Cs,i · sol∗(ts,i)) ∪ {}) = λx.δ A( ni=1 S s Def . δ A = λx.{w ∈ X ∗ | xw ∈ ni=1 (Cs,i · sol∗(ts,i)) ∪ {}} S s = λx.{w ∈ X ∗ | xw ∈ ni=1 (Cs,i · sol∗(ts,i))} 373 wie oben = g ∗(λx.par(ite(χ(Cs,1)(x), ts,1, mt), . . . , ite(χ(Cs,ns )(x), ts,ns , mt))) und ∗ A ∗ (7) A ∗ g (β(s)) = β (g (s)) = β (sol ((reduce ◦ Def . β A = (9) EG∗ )ks (s))) = A β ( Sns i=1 (Cs,i · sol∗(ts,i)) ∪ {}) 1 = g ∗(1). o Demnach erfu¨llt A alle Gleichungen von ES . Satz 18.8 (Fixpunktsatz fu¨r nicht-linksrekursive CFGs) Sei G = (S, BS, Z, R) eine nicht-linksrekursive CFG und X = Z ∪ die einzige L¨osung von EG in Lang(X). S BS. Dann ist solG Beweis. Nach Satz 18.3 (i) ist solG eine L¨osung von EG in Lang(X). Sei sol eine weitere L¨osung von EG in Lang(X). Seien A und A0 die gem¨aß Satz 18.7 aus solG bzw. sol gebildeten coinduktiven L¨osung von BRE ∪ ES in Pow (X). Da Pow (X) eine finale Acc(X)-Algebra ist, stimmt A nach Satz 17.7 mit A0 u¨berein. Also ist solG die einzige L¨osung von EG in Lang(X). o 374 **** Die Erweiterung von BRE um ES erlaubt es, die Erkenner fold regBeh, χ ◦ unfold Bro (siehe 2.11), χ ◦ unfold Norm (siehe 3.12) und fold regCot (siehe 20.6) regul¨arer Sprachen zu Erkennern von L(G) zu erweitern, indem fu¨r alle s ∈ S die Konstante s : 1 → reg durch L(G)s interpretiert wird. Fu¨r die coinduktive L¨osung von BRE ∪ ES in Pow (X) gilt n¨amlich fold A(s) = sA = solG(s) = L(G)s. Diese Erkenner k¨onnen u¨ber die Funktion compCFG von Compiler.hs aufgerufen und getestet werden: Sei s ∈ S. compCFG file s liest die Regeln von G aus der Datei file, u¨bersetzt sie in das iterative Gleichungssystem EG und wendet einen Erkenner von L(G)s auf der Eingabeschleife loopAcc zugefu¨hrte W¨orter an. Alle Erkenner verwenden die Acc(X)-Algebra accBroz, die EG durch die Funktion rhs und ES durch die Gleichungen delta (Var s) x = delta (rhs s) x beta (Var s) = beta (rhs s) implementiert. Unter der Voraussetzung, dass G nicht linksrekursiv ist, garantiert Haskells lazy evaluation dass delta und beta terminieren. 375 19 Interpretation in stetigen Algebren Eine Menge A ist halbgeordnet, wenn es eine reflexive, transitive und antisymmetrische bin¨are Relation, kurz: eine Halbordnung, ≤ auf A gibt. A heißt ω-CPO (ω-complete partially ordered set; ω-vollst¨andige halbgeordnete Menge), wenn A ein bzgl. ≤ kleinstes Element ⊥ und Suprema ti∈Nai aller ω-Ketten a0 ≤ a1 ≤ a2 ≤ . . . von A enth¨alt. Fu¨r jede Menge A liefert die flache Erweiterung, A⊥ =def A + {⊥}, einen ω-CPO: Die zugrundeliegende Halbordnung ≤ ist {(a, b) ∈ A2 | a = ⊥ ∨ a = b}. Der ω-CPO der partiellen Funktionen von A nach B Fu¨r alle f, g : A (→ B, f ≤ g ⇔def def (f ) ⊆ def (g) ∧ ∀ a ∈ A : f (a) = g(a). Die nirgends definierte Funktion Ω : A (→ B mit def (Ω) =def ∅ ist das kleinste Element von A (→ B bzgl. ≤. 376 Jede ω-Kette f0 ≤ f1 ≤ f2 ≤ . . . von A (→ B hat das folgende Supremum: Fu¨r alle a ∈ A, f (a) falls ∃ i ∈ N : a ∈ def (fi), i (ti∈Nfi)(a) =def undefiniert sonst. Ein Produkt A1 ×· · ·×An von n ω-CPOs wie auch die Menge B A aller Funktionen von einer Menge A in einen ω-CPO B bilden selbst ω-CPOs: Die Halbordnungen auf A1, . . . , An bzw. B werden – wie im Abschnitt Kongruenzen und Quotienten beschrieben – zur Halbordnung auf A1 × · · · × An bzw. B A geliftet. Das kleinste Element von B A ist die Funktion Ω mit Ω(a) = ⊥ fu¨r alle a ∈ A, wobei ⊥ das kleinste Element von B ist. Suprema sind komponenten- bzw. argumentweise definiert: Fu¨r alle ω-Ketten a0 = (a0,1, . . . , a0,n) ≤ a1 = (a1,1, . . . , a1,n) ≤ a2 = (a2,1, . . . , a2,n) ≤ . . . von A1 ×· · ·×An bzw. f0 ≤ f1 ≤ f2 ≤ . . . von B A und a ∈ A, (ti∈Nai) =def (ti∈Nai,0, . . . , ti∈Nai,n), (ti∈Nfi)(a) =def ti∈Nfi(a). (2) (3) 377 Seien A, B halbgeordnete Mengen. Eine Funktion f : A → B ist monoton, wenn fu¨r alle a, b ∈ A gilt: a ≤ b ⇒ f (a) ≤ f (b). f ist strikt, wenn f das kleinste Element ⊥A von A auf das kleinste Element ⊥B von B abbildet. Ist A ein Produkt, dann bildet eine strikte Funktion f nicht nur ⊥A, sondern jedes Tupel von A, das ein kleinstes Element enth¨alt, auf ⊥B ab. Seien A, B ω-CPOs. f ist ω-stetig (ω-continuous), wenn f (ti∈Nai) = ti∈Nf (ai) fu¨r alle ω-Ketten a0 ≤ a1 ≤ a2 ≤ . . . von A gilt. ω-stetige Funktionen sind monoton. Alle ω-stetigen Funktionen von A nach B bilden einen CPO, weil Ω und die Suprema ωKetten ω-stetiger Funktionen (siehe (2)) selbst stetig sind. Wir bezeichnen ihn mit A →c B. Sei Σ = (S, BS, BF, F ) eine konstruktive Signatur und A eine Σ-Algebra. A ist monoton, wenn es fu¨r alle s ∈ S eine Halbordnung ≤s,A ⊆ A2s und ein bzgl. ≤s,A kleinstes Element ⊥s,A gibt und fu¨r alle f ∈ F f A monoton ist. A ist ω-stetig, wenn A monoton ist, fu¨r alle s ∈ S As ein ω-CPO ist und fu¨r alle f ∈ F f A ω-stetig ist. 378 Fixpunktsatz von Kleene Sei f : A → B ω-stetig. lfp(f ) =def tn∈N f n(⊥) ist der kleinste Fixpunkt von f (siehe [29]). o Anwendung auf iterative Gleichungssysteme Sei E : V → TΣ(V ) ein iteratives Σ-Gleichungssystem (siehe Kapitel 18) und A eine ωstetige Σ-Algebra. Dann ko¨nnen die Halbordnungen, kleinsten Elemente und Suprema von A, wie in Kapitel 2 beschrieben, nach AV geliftet werden, d.h. AV ist ein ω-CPO. Zuna¨chst definieren wir die Transitionsfunktion EA : AV → AV wie folgt: Fu¨r alle g ∈ AV , E A(g) = g ∗ ◦ E. Nach [4], Proposition 4.13, ist EA ω-stetig. Demnach folgt aus dem Fixpunktsatz von Kleene, dass lfp(EA) =def tn∈N EAn (λx.⊥A) (1) die kleinste L¨osung von E in A ist. Fu¨r alle strikten und ω-stetigen Σ-Homomorphismen h : A → B gilt: h ◦ lfp(EA) = lfp(EB ). (2) 379 Beweis. Fu¨r alle g ∈ AV , h ◦ EA(g) Def . EA = 3.3(1) h ◦ (g ∗ ◦ E) = (h ◦ g ∗) ◦ E = (h ◦ g)∗ ◦ E = EB (h ◦ g). (3) Aus der Striktheit von h und (3) folgt h ◦ EAn (λx.⊥A) = EBn (λx.⊥B ) fu¨r alle n ∈ N (siehe [29]). Daraus erh¨alt man (2), weil h ω-stetig ist. o Iterative Gleichungssysteme dienen u.a. der Spezifikation partieller Funktionen. Sind z.B. zwei partielle Funktionen f, g gegeben, dann ist die Gleichung h = g ◦ h ◦ f zun¨achst nur an eine Bedingung an eine dritte Funktion h. Die Gleichung induziert aber die ω-stetige Transitionsfunktion λh.(g ◦ h ◦ f ) : (A (→ B) → (A (→ B), die nach dem Fixpunktsatz von Kleene einen kleinsten Fixpunkt hat. Dieser wird denotationelle Semantik von h genannt und deshalb h¨aufig mit h gleichgesetzt. 380 19.1 Schleifensemantik Sei Σ = Σ(JavaLight) (siehe Beispiel 4.6). Wir machen das Zustandsmodell javaState von JavaLight (siehe 11.8) zu einer ω-stetigen Σ-Algebra, indem wir die Sorten command und commands durch die Menge der partiellen Funktionen von Store nach Store interpretieren. Nach dem Fixpunktsatz von Kleene (s.o.) hat dann fu¨r alle f : Store → 2 und g : Store (→ Store das iterative Σ-Gleichungssystem E : {x} → TΣ({x}) x 7→ cond1(f, block(seq(g, x))) eine kleinste L¨osung in javaState. Sie liefert uns die Interpretation von loop in javaState: loopjavaState (f, g) =def lfp(EjavaState )(x) : Store (→ Store. Die anderen Operationen von Σ k¨onnen in javaState so interpretiert werden, dass die Haskell-Implementierung von loop in Abschnitt 11.8 fu¨r alle st ∈ Store genau dann terminiert, wenn loopjavaState (f, g)(st) definiert ist! o 381 19.2 Semantik der Assemblersprache StackCom ∗ ¨ Ahnlich wie im vorigen Abschnitt Schleifen als iterative Σ(JavaLight)-Gleichungen dargestellt werden, so l¨asst sich auch jedes Befehlsfolge cs ∈ StackCom ∗ durch ein iteratives Gleichungssystem eqs(cs) repr¨asentieren. Dazu ben¨otigen wir zun¨achst eine konstruktive Signatur Σ(StackCom), aus deren Operationen die Terme von eqs(cs) gebildet sind: Σ(StackCom) = ( {com}, {Z, String}, F ), F = { push : Z → com, load, save, cmp : String → com, pop, add, sub, mul, div, or, and, inv, none : 1 → com, cons, fork : com × com → com }. push, load, save, cmp, pop, add, sub, mul, div, or, and, inv entsprechen den Konstruktoren von StackCom. none, cons, fork ersetzen die Sprungbefehle von StackCom (s.u.). Sei Eqs die Menge der iterativen Σ(Stackcom)-Gleichungssysteme mit Variablen aus N. Die folgendermaßen definierte Funktion eqs : StackCom ∗ → Eqs u¨bersetzt Assemblerprogramme in solche Gleichungssysteme: 382 Sei cs ∈ StackCom ∗ und V (cs) = {0} ∪ {n < |cs| | Jump(n) ∈ cs ∨ JumpF (n) ∈ cs, }. eqs(cs) : V (cs) → TΣ(StackCom)(V (cs)) n 7→ mkTerm(drop(n)(cs)) mkTerm : StackCom ∗ → TΣ(StackCom)(V (cs)) n none c : cs0 7→ fork (n, mkTerm(cs0)) fork (none, mkTerm(cs0)) cons(c, mkTerm(cs0)) falls c = Jump(n) ∧ n < |cs| falls c = Jump(n) ∧ n ≥ |cs| falls c = JumpF (n) ∧ n < |cs| falls c = JumpF (n) ∧ n ≥ |cs| sonst 7→ none 383 Beispiel Das Assemblerprogramm von Abschnitt 12.5 zur Berechnung der Fakulta¨tsfunktion lautet als iteratives Σ(Stackcom)-Gleichungssystem wie folgt: E : {0, 3} → TΣ(StackCom)({0, 3}) 0 7→ cons(push(1), cons(save(fact), cons(pop, 3))) 3 7→ cons(load(x), cons(push(1), cons(cmp(>), fork (none, cons(load(fact), cons(load(x), cons(mul, cons(save(fact), cons(pop, cons(load(x), cons(push(1), cons(sub, cons(save(x), cons(pop, 3)))))))))))))) In Abschnitt 16.5 haben wir informell gezeigt, dass compJava(javaStack ) ein Compiler fu¨r JavaLight bzgl. javaState ist, was laut Kapitel 5 impliziert, dass folgendes Diagramm kommutiert: TΣ(JavaLight) fold javaState g javaState fold javaStack javaStack (1) encode execute g Mach = State (→ State 384 Hier sind • javaStack die in Abschnitt 12.5 definierte, zur JavaLight-Algebra erweiterte Assemblersprache StackCom ∗, • javaState das in Abschnitt 11.8 und 19.1 definierte Zustandsmodell von JavaLight, • Mach der ω-CPO der partiellen Funktionen auf der Zustandsmenge State = Z∗ × ZString , deren Paare aus jeweils einem Kellerinhalt und einer Speicherbelegung bestehen (siehe 12.5), • encode und execute wie in Abschnitt 16.5 definiert. Mach : State (→ State wird zun¨achst zu einer ω-stetigen Σ(StackCom)-Algebra erweitert: Fu¨r alle f, g : State (→ State, ⊗ ∈ {add, sub, mul, div, or, and}, (stack, store) ∈ State, a ∈ Z und x ∈ String, pushMach (i)(stack, store) = (a : stack, store), loadMach (x)(stack, store) = (store(x) : stack, store), saveMach (x)(stack, store) = (stack, update(store)(x)(a)), 385 cmpMach (x)(stack, store) = (1 : s, store) falls ∃ a, b, s : stack = a : b : s ∧ evalRel(x)(a)(b), (0 : s, store) falls ∃ a, b, s : stack = a : b : s ∧ ¬evalRel(x)(a)(b), undefiniert sonst, ( (s, store) falls ∃ a, s : stack = a : s, popMach (stack, store) = undefiniert sonst, ( (op(⊗)(b, a) : s, store) falls ∃ a, b, s : stack = a : b : s, ⊗Mach (stack, store) = undefiniert sonst, ( ((a + 1) mod 2 : stack, store) falls ∃ a, s : stack = a : s, inv Mach (stack, store) = undefiniert sonst, noneMach (stack, store) = (stack, store), consMach (f, g) = g ◦ f, fork Mach (f, g)(stack, store) = f (s, store) falls ∃ s : stack = 0 : s, g(s, store) falls ∃ a, s : stack = a : s ∧ a 6= 0, undefiniert sonst, 386 wobei op(add) = (+), op(sub) = (−), op(mul) = op(and) = (∗), op(div) = (/) und op(or) = sign ◦ (+). Sei cs ∈ StackCom ∗. Nach dem Fixpunktsatz von Kleene hat das iterative Σ(StackCom)Gleichungssystem eqs(cs) (s.o.) eine kleinste L¨osung in Mach. Sie liefert uns die Funktion execute0 in Abschnitt 16.5: Fu¨r alle cs ∈ StackCom ∗ und n ∈ V (cs), execute0(cs) = lfp(eqs(cs)Mach ) : V (cs) → Mach. (4) Tats¨achlich entspricht execute0 der Haskell-Funktion execute in Abschnitt 12.5. Im Folgenden wird Mach so zu einer JavaLight-Algebra erweitert, dass execute und encode JavaLight-homomorph werden und aus der Initialit¨at von TΣ(JavaLight) die Kommutativit¨at von (1) folgt (siehe Kapitel 5). Fu¨r alle Sorten s von JavaLight, Mach s = State (→ State. Fu¨r alle op ∈ {seq, sum, prod, disjunct, conjunct} und f, g : State (→ State, opMach (f, g) = g ◦ f. Fu¨r alle op ∈ {embed, block, encloseS, embedC, embedL, encloseD}, opMach = idState(→State . nilS Mach = nilP Mach = idState . 387 Fu¨r alle x ∈ String und f : State (→ State, assignMach (x, f ) = popMach ◦ saveMach (x) ◦ f. Fu¨r alle f, g, h : State (→ State, condMach (f, g, h) = fork Mach (h, g) ◦ f . Fu¨r alle f, g : State (→ State, cond1Mach (f, g) = fork Mach (idMach , g) ◦ f . Fu¨r alle f, g : State (→ State, loopMach (f, g) = lfp(EMach )(x), wobei E : {x} → TΣ(StackCom)({x}) x 7→ cons(f, fork (none, cons(g, x))) Fu¨r alle f, g : State (→ State, sumsectMach (+, f, g) = g ◦ addMach ◦ f, sumsectMach (−, f, g) = g ◦ subMach ◦ f, prodsectMach (∗, f, g) = g ◦ mulMach ◦ f, prodsectMach (/, f, g) = g ◦ div Mach ◦ f. embedI Mach = pushMach . varMach = loadMach . Fu¨r alle f : State (→ State, notMach (f ) = inv Mach ◦ f . 388 Fu¨r alle x ∈ String und f, g : State (→ State, atomMach (f, x, g) = cmpMach (x) ◦ g ◦ f. Fu¨r alle b ∈ 2, embedB Mach (b) = pushMach (b). Σ-B¨ aume Eine partielle Funktion f : A∗ (→ B heißt pr¨ afixabgeschlossen, wenn fu¨r alle w ∈ A∗ und a ∈ A aus w ∈ def (t) aus wa ∈ def (t) folgt. Eine pr¨afixabgeschlossene partielle Funktion f : A∗ (→ B kann man sich als Baum mit Kantenmarkierungen aus A und Knotenmarkierungen aus B vorstellen. Die Knoten des Baumes sind eindeutig durch den Definitionsbereich von f bestimmt: bezeichnet die Wurzel und jedes Wort w ∈ A+ den Pfad von der Wurzel zum durch ihn bezeichneten Knoten. f () ist die Markierung der Wurzel und f (w) die Markierung des Zielknotens von w. Der Unterbaum von f mit Wurzel w wird eindeutig durch die Funktion λw.f (vw) repr¨asentiert. Sei Σ = (S, BS, BF, F ) eine konstruktive Signatur. Die Menge CTΣ der Σ-B¨ aume ist die gr¨oßte (S ∪BS)-sortige Menge pr¨afixabgeschlossener partieller Funktionen t : N∗ (→ F + ∪BS 389 mit folgenden Eigenschaften: • Fu¨r alle s ∈ S und t ∈ CTΣ,s gibt es n > 0 und e1, . . . , en ∈ S ∪ BS mit t() : e1 × · · · × en → s ∈ F , def (t) ∩ N = {1, . . . , n} und λw.t(iw) ∈ CTΣ,ei fu¨r alle 1 ≤ i ≤ n. • Fu¨r alle X ∈ BS, CTΣ,X = X (wird hier mit der Funktionsmenge 1 → X gleichgesetzt). t ∈ CTΣ heißt endlich, wenn def (t) endlich ist. Ein Σ-Grundterm der Form f (t1, . . . , tn) entspricht dem endlichen Σ-Baum t mit t() = f und λw.t(wi) = ti fu¨r alle 1 ≤ i ≤ n. Umgekehrt schreiben wir auch f (t1, . . . , tn) fu¨r den mo¨glicherweisen unendlichen Σ-Baum t mit n-stelligem t() und λw.t(wi) = ti fu¨r alle 1 ≤ i ≤ n. CTΣ ist eine Σ-Algebra: Fu¨r alle f : e → S ∈ F , (t1, . . . , tn) ∈ CTΣ,e, i ∈ N and w ∈ N∗, ( ti(w) falls 1 ≤ i ≤ n, f CTΣ (t1, . . . , tn)() =def f and f CTΣ (t1, . . . , tn)(iw) =def undefiniert sonst. Die Menge F TΣ der endlichen Σ-B¨aume bildet eine Σ-Unteralgebra von CTΣ. 390 Eine monotone bzw. ω-stetige Σ-Algebra T ist initial in der Klasse PAlg Σ bzw. CAlg Σ aller monotonen bzw. ω-stetigen Σ-Algebren, wenn es zu jeder monotonen bzw. ω-stetigen Σ-Algebra A genau einen strikten (!) und monotonen bzw. ω-stetigen Σ-Homomorphismus A fold A fin : T → A bzw. fold ω : T → A gibt. Alle initialen monotonen bzw. ω-stetigen Σ-Algebren sind durch strikte und monotone bzw. ω-stetige Σ-Isomorphismen miteinander verbunden. Sei Σ⊥ = (S, BS, BF, F ∪ {⊥s : 1 → s | s ∈ S}) und ≤ die kleinste reflexive, transitive und Σ-kongruente S-sortige Relation auf CTΣ⊥ derart, dass fu¨r alle s ∈ S and t ∈ CTΣ⊥,s, ⊥s ≤ t, wobei ⊥s hier den Baum bezeichnet, der aus einem mit ⊥s markierten Blatt besteht. Er bildet offenbar das bzgl. ≤ kleinste Element. F TΣ⊥ ist eine monotone und CTΣ⊥ eine ω-stetige Σ-Algebra (siehe [29]). o Satz 19.3 F TΣ⊥ ist initial in PAlg Σ. CTΣ⊥ ist initial in CAlg Σ. Beweisskizze. Sei A eine monotone und B eine ω-stetige Σ-Algebra. Zwei S-sortige FunkB alt man wie folgt: tionen fold A fin : F TΣ⊥ → A und fold ω : F TΣ⊥ → B erh¨ 391 Fu¨r alle s ∈ S und t ∈ F TΣ⊥,s und u ∈ CTΣ⊥,s, ( ⊥s,A falls t = ⊥s, fold A (t) = def fin A f A(fold A fin (λw.t(iw)), . . . , fold fin (λw.t(iw))) sonst, B fold B ω (u) =def tn∈N fold fin (u|n ), wobei fu¨r alle n ∈ N und w ∈ N∗, ( u|n(w) =def falls |w| ≤ n, u(w) undefiniert sonst. B Zum Nachweis der geforderten Eigenschaften von fold A fin und fold ω , siehe [4, 29]. o Die Faltung fold A ω (u) eines Σ-Baums u in A ist also das Supremum von Faltungen der endlichen Pr¨afixe u|0, u|1, u|2, . . . von u. Satz 19.4 Jedes iterative Σ-Gleichungssystem E : V → TΣ(V ) hat genau eine L¨osung in CTΣ. Beweis. Sei g : V → CTΣ⊥ eine L¨osung von E in CTΣ⊥ . Da lfp(ECTΣ ) die kleinste ⊥ L¨osung von E in CTΣ⊥ ist, gilt: lfp(ECTΣ ) ≤ g. (5) ⊥ 392 Durch Induktion u¨ber n la¨sst sich zeigen, dass fu¨r alle x ∈ V und n ∈ N gilt: n+1 def (g(x)) ∩ Nn ⊆ def (ECT (λx.⊥)(x)) Σ (6) ⊥ (siehe [22], Satz 17). Aus (6) folgt fu¨r alle x ∈ V : def (g(x)) ⊆ def n (tn∈NECT Σ ⊥ (λx.⊥)(x)) Def . lfp(ECT ) Σ = ⊥ def (lfp(ECTΣ )(x)), also g(x) = lfp(ECTΣ )(x) ∈ CTΣ wegen (5) und nach Definition von ≤. ⊥ ⊥ o Ein Σ-Baum heißt rational, wenn er nur endlich viele Unterb¨aume hat. Jeder Pfad eines rationalen Baums ist endlich oder periodisch. Rationale B¨aume k¨onnen als endliche Graphen dargestellt werden, deren Zyklen aus den Pfad-Perioden entstehen. Solche Graphen entsprechen iterativen Gleichungssystemen (siehe Kapitel 18). Ein Σ-Baum ist also genau dann rational, wenn er zum Bild der L¨osung eines iterativen Σ-Gleichungssystems in CTΣ⊥ oder CTΣ geh¨ort. Beispiel Sei Σ = Σ(JavaLight). Die eindeutige L¨osung sol : {x} → CTΣ des iterativen ΣGleichungssystems E : {x} → TΣ({x}) 393 von Abschnitt 19.1 hat folgende Graphdarstellung: Fu¨r alle w ∈ N∗, o Beispiel Sei Σ = Σ(StackCom) und cs das Assemblerprogramm von Beispiel 12.6. Die eindeutige L¨osung sol : {0, 3} → CTΣ des iterativen Σ-Gleichungssystems eqs(cs) : {0, 3} → TΣ({0, 3}) von Abschnitt 19.2 hat folgende Graphdarstellung: 394 sol(3) = sol(0) = 395 Offenbar repr¨asentiert jeder Pfad des Σ-Baums sol(0) eine – m¨oglicherweise unendliche – Kommandofolge, die einer Ausfu¨hrungssequenz von cs entspricht. o Beispiel Sei Σ = Σ(StackCom). Die eindeutige L¨osung sol : {x} → CTΣ0 des iterativen ΣGleichungssystems E : {x} → TΣ({x}), dessen kleinste L¨osung in Mach den Konstruktor loop von Σ(StackCom) in Mach interpretiert (siehe 19.2), hat folgende Graphdarstellung: sol(x) = 396 Sei cs ∈ StackCom ∗. Da fold Mach strikt, ω-stetig und Σ-homomorph ist, folgt ω execute0(cs) = fold Mach ◦ lfp(eqs(cs)CTΣ ) : V (cs) → Mach ω (7) aus (2) und (4). Die Ausfu¨hrung von cs im Zustand state ∈ State liefert also dasselbe – m¨oglicherweise undefinierte – Ergebnis wie die Faltung des Σ-Baums lfp(eqs(cs)CTΣ )(state) in Mach. o Cosignaturen und ihre Algebren Die eindeutige L¨osung eines iterativen Σ-Gleichungssystems E in CTΣ l¨asst sich nicht nur als kleinsten Fixpunkt von ECTΣ darstellen, sondern auch als Entfaltung einer Coalgebra. Dies folgt aus der Tatsache, dass CTΣ eine finale coΣ-Algebra ist, wobei coΣ die folgendermaßen aus (der konstruktiven Signatur) Σ = (S, BS, BF, F ) gebildete destruktive Signatur ist: ` coΣ =def (S, BS, BF, {ds : s → f :e→s∈F e | s ∈ S} ∪ {πi : s1 × · · · × sn → si | n > 1, s1, . . . , sn ∈ S ∪ BS, 1 ≤ i ≤ n}). Jeder Produkttyp s1 × · · · × sn wird hier als zus¨atzliche Sorte betrachtet. Die Projektionen πi : s1 × · · · × sn → si, 1 ≤ i ≤ n, bilden seine Destruktoren. ` Hier bezeichnet f :e→s∈F e das Coprodukt der Domains e aller Konstrukturen von F mit Range s. 397 Dementsprechend wird ds in einer coΣ-Algebra A als Funktion von As in die disjunkte Vereinigung ] Ae = {(a, f ) | a ∈ Ae, f : e → s ∈ F } f :e→s∈F der Tr¨agermengen jener Domains interpretiert. Z.B. lautet die Interpretation von ds in CTΣ wie folgt: Fu¨r alle s ∈ S und t ∈ CTΣ,s mit n-stelliger Operation t(), Σ (t) = dCT def ((λw.t(1w), . . . , λw.t(nw)), t()). s CTΣ ist also nicht nur eine Σ-Algebra, sondern auch eine coΣ-Algebra. Mehr noch: Satz 19.5 CTΣ ist eine finale coΣ-Algebra. Beweis. Sei A eine coΣ-Algebra. Die S-sortige Funktion unfold A : A → CTΣ ist wie folgt definiert: Fu¨r alle s ∈ S, a ∈ As, i > 0 und w ∈ N∗, unfold A(a)() = f, ( unfold A(ai)(w) falls π1(dA s (a)) = (a1 , . . . , an ) ∧ 1 ≤ i ≤ n, A unfold (a)(iw) = undefiniert sonst. 398 Der Nachweis der geforderten Eigenschaften von unfold A wird in [29] gefu¨hrt. o Sei E : V → TΣ(V ) ein iteratives Σ-Gleichungssystem. E macht TΣ(V ) zur coΣ-Algebra T E : Fu¨r alle s ∈ S, f : e → s ∈ F , t ∈ TΣ(V )e und x ∈ Vs, TsE =def TΣ(V )s, E dTs (f t) =def (t, f ), E E dTs (x) =def dTs (E(x)). E (8) unfold T ◦ incV : V → CTΣ l¨ost E in CTΣ (siehe [29]). (9) g : V → CTΣ l¨ost E genau dann in CTΣ, wenn g ∗ : T E → CTΣ coΣ-homomorph ist (siehe [29]). Satz 19.6 (coalgebraische Version von Satz 19.4) Jedes iterative Σ-Gleichungssystem E : V → TΣ(V ) hat genau eine L¨osung in CTΣ. Beweis. Wegen (8) hat E eine Lo¨sung in CTΣ. Angenommen, g, h : V → CTΣ lo¨sen E in CTΣ. Dann sind g ∗, h∗ : T E → CTΣ wegen (9) coΣ-homomorph. Da CTΣ eine finale coΣ-Algebra ist, gilt g ∗ = h∗, also g = g ∗ ◦ incV = h∗ ◦ incV = h. o 399 Sei cs ∈ StackCom ∗. Aus (7), (8) und Satz 19.4 (oder 19.6) folgt, dass execute0 die Entfaltung von V (cs) in CTΣ mit der Faltung der entstandenen Σ-B¨aume in Mach verknu¨pft: V (cs) incV execute0 fold Mach ω = g TE unfold Mach f TE CTΣ 400 20 Cotermalgebren Die Faltung von t in Lang(X) kann zu riesigen λ-Ausdru¨cken fu¨hren. Wegen ihrer Finalita¨t hat Lang(X) jedoch eine Fixpunkt-Eigenschaft, die als Lambeks Lemma bekannt ist und aus der sich eine – weniger platzaufw¨andige – Baumdarstellung der Elemente von Lang(X) ableiten la¨sst. Fu¨r die finale Algebra A einer beliebigen destruktiven Signatur Σ = (S, BS, BF, F ) lautet Lambeks Lemma wie folgt: Sei s ∈ S und Ds = {d1 : s1 → e1, . . . , d1 : sn → en} die Menge aller Destruktoren von F mit Domain s. Die Produktextension von Ds genannte Funktion A hdA 1 , . . . , dn i : As → Ae1 × . . . × Aen A a 7→ (dA 1 (a), . . . , dn (a)) ist bijektiv. Es gibt also eine Funktion cA s : Ae1 × . . . × Aen → As A A A A A mit cA s ◦ hd1 , . . . , dn i = idAs und hd1 , . . . , dn i ◦ cs = idAe1 ×...×Aen . 401 Da die Typen e1, . . . , en Ranges von Destruktoren sind, ko¨nnen Potenztypen darunter sein. Erlaubt man diese im Domain eines Konstruktors, dann ist cA s offenbar eine Interpretation des Konstruktors cs : e1 × · · · × en → s in A. In der von Lambeks Lemma induzierten Baumdarstellung eines Elementes von A sind alle inneren Knoten mit cs markiert, alle Bla¨tter mit Basiselementen und alle Kanten mit Destruktoren, die mit denen von F u¨bereinstimmen bzw. aus ihnen abgeleitet sind. Wir nennen solche Ba¨ume Σ-Coterme: Σ-Coterme Sei Σ = (S, BS, BF, F ) eine destruktive Signatur und D = {dx : s → e | d : s → eX ∈ F, x ∈ X}. Im Fall X = 1 schreiben wir d anstelle von d. Die Elemente der gr¨oßten S-sortigen Menge coTΣ pr¨afixabgeschlossener partieller Funktionen t : D∗ (→ 1 + ∪BS mit folgenden Eigenschaften heißen Σ-Coterme: • Fu¨r alle s ∈ S, t ∈ coTΣ,s und d : s → e ∈ D, t() = , λw.t(dw) ∈ coTΣ,e und def (t) ∩ D = {d ∈ D | dom(d) = s}. • Fu¨r alle X ∈ BS, coTΣ,X = X (wird hier mit der Funktionsmenge 1 → X gleichgesetzt). 402 ε head tail 0 ε tail head 1 ε tail head 2 ε Stream(N)-Coterm, der den Strom aller natu ¨rlichen Zahlen darstellt ε δx δx ε ε δy δz δx ε ε δz δy 1 β ε ε 0 β ε δy ε 1 β 0 β δx δz ε ε δy ε δz ε Acc({x, y, z})-Coterm, der einen Akzeptor der Sprache {w ∈ {x, y, z}∗ | w enth¨alt x oder z} darstellt 403 Sei s ∈ S, {d1, . . . , dn} = {d ∈ F | dom(d) = s} und t ∈ coTΣ,s. coT Anschaulich gesprochen, ist im Fall ran(di) ∈ S ∪ BS di Σ (t) der Unterbaum von t, auf den die von seiner Wurzel ausgehende und mit di markierte Kante von t zeigt. Andernfalls coT gibt es e ∈ S ∪ BS und X ∈ BS mit ran(di) = eX und fu¨r alle x ∈ X ist di Σ (t)(x) der Unterbaum von t, auf den die von ausgehende und mit λs.di(s)(x) markierte Kante von t zeigt. In Haskell l¨asst sich t als das Objekt coTΣ Con s {attr 1 = d1 Σ (t)} (t),..., attr n = dcoT n oder coTΣ Con s d1 Σ (t) (t) ... dcoT n des Datentyps data Cot s = Con s {attr 1 :: ran(d1),..., attr n :: ran(dn)} darstellen (siehe Beispiele 10.3 und 10.4). Offenbar haben alle Σ-Coterme derselben Sorte dieselbe Struktur und dieselbe Markierung innerer Knoten, n¨amlich . Allein in den Markierungen der Bl¨atter – die stets Elemente von Basismengen sind – k¨onnen sich Σ-Coterme voneinander unterscheiden. 404 Demnach gilt fu¨r alle s ∈ S, t, t0 ∈ coTΣ,s und w ∈ D∗, def (t) = def (t0) ∧ t(w) = ⇔ t0(w) = . (1) (1) gilt nicht mehr, wenn der Range eines Destruktors d ein Summentyp e1 + · · · + en sein kann (der als disjunkte Vereinigung der Interpretationen von e1, . . . , en interpretiert wird). Anschaulich gesprochen, legt jeder Aufruf von d : s → e1 + · · · + en sein Ergebnis an einem von n “Ausg¨angen” ab. Die dem Aufruf entsprechende Kante eines Σ-Coterms muss deshalb neben d den Index 1 ≤ i ≤ n des gew¨ahlten Ausgangs tragen (siehe [29]). Ohne Summentypen l¨asst sich coTΣ wie folgt zu einer Σ-Algebra erweitern: Fu¨r alle s ∈ S, t ∈ coTΣ,s, d : s → eX ∈ F , x ∈ X und w ∈ D∗. dcoTΣ (t)(x)(w) = t(dxw). Sei A eine Σ-Algebra. Fu¨r alle d : s → eX und x ∈ X wird dx : s → e in A wie folgt interpretiert: Fu¨r alle a ∈ As, A dA x (a) =def d (a)(x). 405 Die S-sortige Funktion unfold A : A → coTΣ ist wie folgt definiert: Fu¨r alle s ∈ S, a ∈ As, d : s0 → e ∈ D und w ∈ D∗, unfold A s (a)() = , ( unfold A s (a)(dw) = A 0 unfold A e (d (a))(w) falls s = s, undefiniert sonst. Der folgende Satz verallgemeinert Satz 2.9 von DAut(X, Y ) auf beliebige destruktive Signaturen: Satz 20.1 Sei Σ = (S, BS, BF, F ) eine destruktive Signatur. coTΣ ist eine finale Σ-Algebra. Beweis. Sei A eine Σ-Algebra. unfold A ist Σ-homomorph: Fu¨r alle d : s → eX ∈ F , a ∈ As, x ∈ X und w ∈ D∗, A A A dcoTΣ (unfold A s (a))(x)(w) = unfold s (a)(dx w) = unfold e (dx (a))(w) A A A = unfold A e (d (a)(x))(w) = unfold eX (d (a))(x)(w). unfold A ist eindeutig: Sei h : A → coTΣ ein Σ-Homomorphismus und a ∈ As. 406 Fu¨r alle s ∈ S, hs(a)() = = unfold A s (a)(). Fu¨r alle s ∈ BS, hs(a)() = a = unfold A s (a)(). Fu¨r alle d : s → eX ∈ F , x ∈ X und w ∈ D∗, hs(a)(dxw) = dcoTΣ (hs(a))(x)(w) = heX (dA(a))(x)(w) = he(dA(a)(x))(w) = he(dA x (a))(w). Demnach ist h durch dieselben rekursiven Gleichungen definiert wie unfold A, stimmt also mit unfold A u¨berein. o Nach Satz 3.4 (4) kann man die Eindeutigkeit von unfold A auch aus folgender Bedingung schließen: Die Diagonale von coTΣ2 ist die einzige Σ-Kongruenz auf coTΣ. (2) Um (2) zu zeigen, verallgemeinern wir die fu¨r Σ = DAut(X, Y ) definierte Fortsetzung runA von δ A auf W¨orter u¨ber X (siehe Finale Algebren) zur S-sortigen Fortsetzung ∗ {runA s : As → (D (→ A + ∪BS) | s ∈ S} auf W¨orter u¨ber D: 407 Fu¨r alle s ∈ S ∪ BS, a ∈ As, d : s0 → e ∈ D und w ∈ D∗, runA s (a)() = a, ( runA s (a)(dw) = A 0 runA e (d (a))(w) falls s = s, undefiniert sonst. Unter Verwendung von runA lautet die Definition von unfold A wie folgt: Fu¨r alle s ∈ S, a ∈ As, w ∈ D∗ und d : s0 → e ∈ D, unfold A s (a)() = , ( unfold A s (a)(wd) = falls e ∈ S, runA s (a)(wd) falls e ∈ BS. Lemma 20.2 Sei Σ = (S, BS, BF, F ) eine destruktive Signatur und ∼ eine Σ-Kongruenz auf coTΣ. Fu¨r alle t ∼ t0 und w ∈ D∗ gilt runcoTΣ (t)(w) ∼ runcoTΣ (t0)(w). Beweis durch Induktion u ¨ber |w|. Sei t ∼ t0. runcoTΣ (t)() = t ∼ t0 = runcoTΣ (t0)(). Fu¨r alle d : s → eX ∈ F , x ∈ X und w ∈ D∗, coTΣ runcoTΣ (t)(dxw) = runcoTΣ (dx = runcoTΣ (t0)(dxw). (t))(w) Induktionsvor . ∼ coTΣ runcoTΣ (dx (t0))(w) o 408 Sei DS = {d : s → e ∈ D | e ∈ S}. Lemma 20.3 Fu¨r alle s ∈ S, t ∈ coTΣ,s, v ∈ DS ∗ und d : s0 → e ∈ D, ( λw.t(vdw) falls e ∈ S, coT runs Σ (t)(vd) = t(vd) falls e ∈ BS. Beweis durch Induktion u ¨ber |v|. Sei t ∼ t0. Fu¨r alle d : s → eX ∈ F und x ∈ X, coTΣ runs coTΣ (t)(dx) = rune coTΣ (dx coTΣ (t))() = rune (dcoTΣ (t)(x))() = dcoTΣ (t)(x) = λw.t(dxw). Fu¨r alle d0 : s → eX ∈ F , x ∈ X, v ∈ DS ∗ und d ∈ DS, coTΣ runs 0coTΣ (t)(d0xvd) = runcoTΣ (dx (t))(vd) Induktionsvor . = 0coTΣ λw.dx (t)(vdw) = λw.d0coTΣ (t)(x)(vdw) = λw.t(d0xvdw). Fu¨r alle d0 : s → eX ∈ F , x ∈ X, v ∈ DS ∗ und d ∈ DS \ D, coTΣ runs 0coTΣ (t)(d0xvd) = runcoTΣ (dx = d0coTΣ (t)(x)(vd) = t(d0xvd). (t))(vd) Induktionsvor . = 0coTΣ dx (t)(vd) o 409 Satz 20.4 Sei ∼ eine Σ-Kongruenz auf coTΣ, s ∈ S und t ∼s t0. Dann gilt t = t0. Damit folgt aus der Σ-Homomorphie von unfold A (siehe Satz 20.1) und Satz 3.4 (4), dass coTΣ eine finale Σ-Algebra ist. Beweis. t() = = t0(). Fu¨r alle w ∈ DS ∗ und d : s → e ∈ D mit e ∈ S gilt t(wd) = = t0(wd). Fu¨r alle w ∈ DS ∗ und d : s → e ∈ D mit e ∈ BS, 20.3 20.2 20.3 t(wd) = runcoTΣ (t)(wd) = runcoTΣ (t0)(wd) = t0(wd). Fu¨r alle w ∈ D∗ \ DS ∗ und d ∈ D sind t(wd) und t0(wd) undefiniert. o Beispiel 20.5 Sei Σ = DAut(X, Y ). Da finale Algebren isomorph sind, folgt aus den S¨atzen 2.9 und 20.1 (oder 20.4), dass coTΣ und Beh(X, Y ) Σ-isomorph sind. Tats¨achlich sehen sich die Tra¨germengen von coTΣ und Beh(X, Y ) sehr a¨hnlich: Sei D = {δx : state → state | x ∈ X}. coTΣ besteht aus allen Funktionen von D∗ · β nach Y und Beh(X, Y ) aus allen Funktionen von X ∗ nach Y . Beide Funktionsmengen sind isomorph: 410 Aufgabe Sei g : D∗ · β → X ∗ definiert durch g(β) = und g(δxw) = x · g(w) fu¨r alle ∗ ∗ x ∈ X und w ∈ D∗ · β. Zeigen Sie, dass die Abbildung ξ : Y X → Y D ·β mit ξ(f ) = f ◦ g ∗ fu¨r alle f ∈ Y X ein Σ-Isomorphismus ist. o Coterme sind demnach so etwas wie verallgemeinerte Verhaltensfunktionen. Auch der Realisierungsbegriff (siehe Unter- und Bildalgebren) l¨asst sich von Beh(X, Y ) auf coTΣ u¨bertragen: Fu¨r alle Σ-Algebren, s ∈ S und a ∈ As, (A, a) realisiert t ∈ coTΣ,s ⇔def unfold A s (a) = t. 20.6 Cotermbasierter Erkenner regul¨ arer Sprachen Sei Σ = Acc(X) (siehe 2.2). Die Σ-Algebra ist coTΣ ist im Haskell-Modul Compiler.hs durch accCot implementiert. Unter Verwendung der Haskell-Darstellung von Cotermen als Elemente des rekursiven Datentyps DAutCot(x)(y) (siehe Beispiel 10.4) machen wir coTΣ wie folgt zur Reg(CS)Algebra (regCot im Haskell-Modul Compiler.hs): Fu¨r alle C ∈ CS, f, g : X → coTΣ, b, c ∈ 2 und t ∈ coTΣ, 411 epscoTΣ = State(λx.mtcoTΣ )(1), mtcoTΣ = State(λx.mtcoTΣ )(0), C coTΣ = State(λx.if (x ∈ C) = 1 then epscoTΣ else mtcoTΣ )(0), parcoTΣ (State(f )(b), State(g)(c)) = State(λx.parcoTΣ (f (x), g(x)))(max{b, c}), seq coTΣ (State(f )(1), State(g)(c)) = State(λx.parcoTΣ (seq coTΣ (f (x), State(g)(c)), g(x))(c), seq coTΣ (State(f )(0), t) = State(λx.seq coTΣ (f (x), t))(0), itercoTΣ (State(f )(b)) = State(λx.seq coTΣ (f (x), itercoTΣ (State(f )(b))))(1). Sei Ψ die Bisignatur von Beispiel 17.4 und BRE das rekursive Ψ-Gleichungssystem von Abschnitt 3.11. BRE hat in der finalen Σ-Algebra coTΣ die durch regCot definierte coinduktive Lo¨sung C. Nach Satz 17.7 ist C die einzige coinduktive L¨osung von BRE in regCot. Daru¨berhinaus folgt fold regCot = unfold 0Bro(CS) : Bro(CS) → coTΣ aus Satz 17.7 und damit die Σ-Homomorphie dieser Faltung. Wegen ihrer Eindeutigkeit und ∗ der Σ-Homomorphie der in Abschnitt 2.7 definierten Funktion χ : P(X ∗) → 2X und der in ∗ ∗ Beispiel 20.5 – fu¨r eine beliebige Ausgabemenge Y – definierten Funktion ξ : 2X → 2D ·β folgt 412 fold regCot = unfold 0Bro(CS) = ξ ◦ χ ◦ unfold Bro(CS) = ξ ◦ χ ◦ fold Lang(X), wobei unfold Bro(CS) wie in Abschnitt 2.11 die Entfaltung von Zusta¨nden des BrzozowskiAutomaten in der finalen Σ-Algebra Pow (X) ist. o 413 Literatur Literatur [1] M. Bonsangue, J. Rutten, A. Silva, An Algebra for Kripke Polynomial Coalgebras, Proc. 24th LICS (2009) 49-58 [2] J.A. Brzozowski, Derivatives of regular expressions, Journal ACM 11 (1964) 481–494 [3] J.H. Conway, Regular Algebra and Finite Machines, Chapman and Hall 1971 [4] J.A. Goguen, J.W. Thatcher, E.G. Wagner, J.B. Wright, Initial Algebra Semantics and Continuous Algebras, Journal ACM 24 (1977) 68-95 [5] S. Goncharov, L. Schro¨der, T. Mossakowski, Completeness of Global Evaluation Logic, Proc. MFCS 2006, Springer LNCS 4162 (2006) 447–458 [6] H.H. Hansen, J. Rutten, Symbolic Synthesis of Mealy Machines from Arithmetic Bitstream Functions, CWI Report SEN-1006 (2010) [7] R. Hinze, Functional Pearl: Streams and Unique Fixed Points, Proc. 13th ICFP (2008) 189-200 414 [8] R. Hinze, D.W.H. James, Proving the Unique-Fixed Point Principle Correct, Proc. 16th ICFP (2011) 359-371 [9] J.E. Hopcroft, R. Motwani, J.D. Ullman, Introduction to Automata Theory, Languages, and Computation, Addison Wesley 2001; deutsch: Einfu ¨hrung in die Automatentheorie, Formale Sprachen und Komplexit¨atstheorie, Pearson Studium 2002 [10] G. Hutton, Fold and unfold for program semantics, Proc. 3rd ICFP (1998) 280-288 [11] B. Jacobs, Introduction to Coalgebra, draft, Radboud University Nijmegen 2012 [12] B. Jacobs, A Bialgebraic Review of Deterministic Automata, Regular Expressions and Languages, in: K. Futatsugi et al. (eds.), Goguen Festschrift, Springer LNCS 4060 (2006) 375–404 [13] B. Klin, Bialgebras for structural operational semantics: An introduction, Theoretical Computer Science 412 (2011) 5043-5069 [14] D. Knuth, Semantics of Context-Free Languages, Mathematical Systems Theory 2 (1968) 127-145; Corrections: Math. Systems Theory 5 (1971) 95-96 [15] D. Kozen, Realization of Coinductive Types, Proc. Math. Foundations of Prog. Lang. Semantics 27, Carnegie Mellon University, Pittsburgh 2011 [16] C. Kupke, J. Rutten, On the final coalgebra of automatic sequences, in: Logic and Program Semantics, Springer LNCS 7230 (2012) 149-164 415 [17] P.J. Landin, The Mechanical Evaluation of Expressions, Computer Journal 6 (1964) 308–320 [18] W. Martens, F. Neven, Th. Schwentick, Simple off the shelf abstractions for XML Schema, SIGMOD Record 36,3 (2007) 15-22 [19] E. Moggi, Notions of Computation and Monads, Information and Computation 93 (1991) 55-92 [20] T. Mossakowski, L. Schr¨oder, S. Goncharov, A Generic Complete Dynamic Logic for Reasoning About Purity and Effects, Proc. FASE 2008, Springer LNCS 4961 (2008) 199-214 [21] R.N. Moll, M.A. Arbib, A.J. Kfoury, Introduction to Formal Language Theory, Springer (1988) [22] P. Padawitz, Church-Rosser-Eigenschaften von Graphgrammatiken und Anwendungen auf die Semantik von LISP, Diplomarbeit, TU Berlin 1978 [23] P. Padawitz, Formale Methoden des Systementwurfs, http://fldit-www.cs.uni-dortmund.de/∼peter/TdP96.pdf ¨ [24] P. Padawitz, Ubersetzerbau, TU Dortmund 2008, http://fldit-www.cs.uni-dortmund.de/∼peter/Cbau.pdf 416 [25] P. Padawitz, Modellieren und Implementieren in Haskell, TU Dortmund 2013, http://fldit-www.cs.uni-dortmund.de/∼peter/Essen.pdf [26] P. Padawitz, Logik fu ¨r Informatiker, TU Dortmund 2013, http://fldit-www.cs.uni-dortmund.de/∼peter/LogikPad.pdf [27] P. Padawitz, Algebraic Model Checking, in: F. Drewes, A. Habel, B. Hoffmann, D. Plump, eds., Manipulation of Graphs, Algebras and Pictures, Electronic Communications of the EASST Vol. 26 (2010) [28] P. Padawitz, From Modal Logic to (Co)Algebraic Reasoning, TU Dortmund 2013 [29] P. Padawitz, Fixpoints, Categories, and (Co)Algebraic Modeling, TU Dortmund 2014 [30] H. Reichel, An Algebraic Approach to Regular Sets, in: K. Futatsugi et al., Goguen Festschrift, Springer LNCS 4060 (2006) 449-458 [31] W.C. Rounds, Mappings and Grammars on Trees, Mathematical Systems Theory 4 (1970) 256-287 [32] J. Rutten, Processes as terms: non-wellfounded models for bisimulation, Math. Struct. in Comp. Science 15 (1992) 257-275 [33] J. Rutten, Automata and coinduction (an exercise in coalgebra), Proc. CONCUR ’98, Springer LNCS 1466 (1998) 194–218 417 [34] J. Rutten, Behavioural differential equations: a coinductive calculus of streams, automata, and power series, Theoretical Computer Science 308 (2003) 1-53 [35] J. Rutten, A coinductive calculus of streams, Math. Struct. in Comp. Science 15 (2005) 93-147 [36] A. Silva, J. Rutten, A coinductive calculus of binary trees, Information and Computation 208 (2010) 578–593 [37] D. Turi, G. Plotkin, Towards a Mathematical Operational Semantics, Proc. LICS 1997, IEEE Computer Society Press (1997) 280–291 [38] J.W. Thatcher, E.G. Wagner, J.B. Wright, More on Advice on Structuring Compilers and Proving Them Correct, Theoretical Computer Science 15 (1981) 223-249 [39] J.W. Thatcher, J.B. Wright, Generalized Finite Automata Theory with an Application to a Decision Problem of Second-Order Logic, Theory of Computing Systems 2 (1968) 57-81 [40] Ph. Wadler, Monads for Functional Programming, Proc. Advanced Functional Programming, Springer LNCS 925 (1995) 24-52 418 Index C-Abschluss, 343 E-Normalform, 74 E-Reduktionsrelation, 73 ¨ E-Aquivalenz, 71 S-sortige Funktion, 20 S-sortige Menge, 20 S-sortige Relation, 60 TΣ(V ), 27 DAut(X, Y ), 26 TΣ, 28 Σ(JavaLight), 93 Σ(XMLstore), 96 Σ-Algebra, 29 Σ-Baum, 389 Σ-Coterm, 402 Σ-Gleichung, 63 Σ-Homomorphismus, 30 Σ-Isomorphismus, 30 Σ-Kongruenz, 61 Σ(G), 90 λ-Abstraktion, 27, 178 λ-Abstraktion, 18 ω-CPO, 376 ω-stetig, 378 ω-stetige Funktion, 378 hai, 52 →c, 378 T(S,BS), 19 n-Tupel, 15 state(ϕ), 155 (++), 187 ¨ Aquivalenzabschluss, 61 abgeleitetes Attribut, 232 Abl(G), 102 Ableitungsbaum, 102 Ableitungsrelation, 87 419 absolute Adresse, 255 abstrakte Syntax, 90 akzeptierte Baumsprache, 54 Akzeptor, 26 all, 195 any, 195 Applikationsoperator, 181 Attribut, 201 Basisadresse, 255 Baumautomat, 54 Befehlsz¨ahler, 254 Bild, 18 Bildalgebra, 57 bind-Operatoren, 117 bottom-up-Compiler, 150 Brzozowski-Automat, 36 Brzozowski-Gleichungen, 76 CFG, 82 charakteristische Funktion, 19 Coalgebra, 29 Coinduktion, 341, 344 Coinduktionsprinzip, 63 Compiler, 9 const, 182 Coprodukt, 397 curry, 184 denotationelle Semantik, 362, 380 destruktive Signatur, 22 Destruktor, 22 Diagonale, 60 disjunkt, 17 Display, 255 dom, 21 Domain, 21 drop, 188 eindeutig, 99 elem, 195 Erkenner einer kontextfreien Sprache, 126 Erkenner einer regul¨aren Sprache, 47 Erkennung einer Sprache, 52 420 Erreichbarkeitsfunktion, 38 Exceptionfunktor, 114 execute, 249, 261 executeCom, 248 freie Algebra, 40 freie Variable, 28 Functor, 296 Funktion h¨oherer Ordnung, 179 Funktionsiteration, 184 Funktionsprodukt, 16 Funktor, 112 fail, 297 Faltung, 40 field label, 201 generischer Compiler, 124 filter, 195 Gleichungssystem einer CFG, 365 finale Algebra, 43 goto-Tabelle, 155 Fixpunkt-Eigenschaft, 401 Grundinstanz, 69 Fixpunktsatz fu¨r CFGs, 365 Fixpunktsatz fu¨r nicht-linksrekursive CFGs, Grundterm, 28 guard, 298 374 Fixpunktsatz von Kleene, 379 halbgeordnete Menge, 376 flache Erweiterung von A, 376 head, 187 flip, 183 id, 182 fold, 40 Identita¨t, 15 fold2, 192 Identit¨atsfunktor, 113 foldl, 191 Individuenvariable, 177, 185 foldr, 193 Induktion, 334 421 Induktionsprinzip, 58 init, 188 initiale Algebra, 40, 391 initialer Automat, 52 Instanz, 178 Instanz eines Terms, 69 Instanz eines Typs, 185 Interpreter, 9, 109 Invariante, 56 isomorph, 30 iteratives Gleichungssystem, 363 JavaLight, 83 JavaLightP, 263 javaStackP, 267 Kategorie, 112 Kern, 62 Komponente eines Tupels, 15 Kompositionsoperator, 181 Konditional, 27 Kongruenz modulo C, 343 Konkatenation, 16 konstanter Funktor, 113 konstruktive Signatur, 22 Konstruktor, 22 Konstruktor einer Grammatikregel, 90 kontextfreie Grammatik, 82 korrekter Parser, 123 La¨nge eines Worts, 15 L¨osung eines iterativen Gleichungssystems, 363 LAG-Algorithmus, 293 Lambeks Lemma, 336, 401 last, 188 lines, 190 linksrekursive Grammatik, 87 Liste, 15 Listenfunktor, 113 Listenkomprehension, 196 LL-Compiler, 321 LR(k)-Grammatik, 151 LR-Automat fu¨r G, 155 422 map, 189 mapM, 311 Mengenfunktor, 113 Monad, 297 Monade, 115 MonadPlus, 297 Monoid, 31 monomorph, 185 monotone Algebra, 378 monotone Funktion, 378 mplus, 297 msum, 310 mzero, 297 natu¨rliche Abbildung, 62 notElem, 195 Operation, 21 Parser, 9, 111 Paull-Unger-Verfahren, 65 Plusmonade, 119 polymorph, 185 Potenzfunktor, 114 Potenzmenge, 17 pra¨fixabgeschlossen, 389 Produkt, 15 Produktextension, 16, 401 Projektion, 15 Quotientenalgebra, 61 ran, 21 Range, 21 rational, 393 Realisierung einer Verhaltensfunktion, 52 Realisierung eines Coterms, 411 Rechtsreduktion, 150 Reduktionsfunktion, 74 Reg(CS), 24 Regel, 82 regul¨are Sprache, 47 regul¨arer Ausdruck, 24 rekursives Ψ-Gleichungssystem, 335 Relativadresse, 255 423 Resultatadresse, 275 return, 297 SAB, 91 Scanner, 8 Sektion, 180 sequence, 310 Signatur, 21 Sprache u¨ber X, 35 Sprache einer CFG, 99 Sprache eines regul¨aren Ausdrucks, 47 StackCom, 248 State, 248 statischer Vorg¨anger, 255 strikt, 378 strukturell-operationelle Semantik, 361 Substitution, 68 Summe, 17 Summenextension, 336 symbolische Adressen, 256 Symboltabelle, 265 syntaktische Kongruenz, 67 syntaktisches Monoid, 67 Syntaxbaum, 91, 99 tail, 187 take, 188 Term, 27 Termalgebra, 39 Termapplikation, 27 Termauswertung, 40 top-down-Parser, 129 Tr¨agermenge, 29 transientes Attribut, 232 Transitionsfunktor, 114 Transitionsmonoid, 66 Tuplung, 27 Typdeskriptor, 261 Typen, 19 Typklassen, 208 Typkonstruktor, 177 Typvariable, 177 424 uncurry, 184 unfold, 43 unit-Typ, 177 unlines, 190 Unteralgebra, 56 unwords, 190 update, 182 Urbild, 18 zipWithM, 311 Variablenbelegung, 40 vererbtes Attribut, 232 Verhaltensfunktion, 34 Verhaltenskongruenz, 63 when, 310 Wildcard, 185 Word(G), 99 words, 190 Wort, 15 Wortalgebra, 99 zip, 189 zipWith, 189 425
© Copyright 2024