¨ Ubersetzerbau Algebraic Compiler Construction Peter Padawitz, TU Dortmund

¨
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> [email protected] </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> [email protected] </email>
<email> [email protected] </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
[email protected]
EmbedS
PersonE
Id 30 Parts
Al Jones Emails
Email
J38H
Emails
Id 10 Supplier
[email protected] Email None J38H1
[email protected]
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 []
[email protected]
[]
()
()
()
IG8 10 One
J38H 30 Two
Al Jones []
[]
I18F 100 [email protected] [email protected] ()
()
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