Datenstrukturen, Algorithmen und Programmierung 1

Werbung
Datenstrukturen, Algorithmen und
Programmierung 1
Vorlesungsskriptum
Wintersemester 2006/2007
Prof. Dr. Bernhard Steffen
Prof. Dr. Markus Müller-Olm
Dr. Oliver Rüthing
Fachbereich Informatik
Universität Dortmund
2005
Inhaltsverzeichnis
1 Einführung
1.1
1.2
1.3
2
Was ist Informatik? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
1.1.1
Geschichtliches zur Computer–Entwicklung . . . . . . . . . . . . . .
5
1.1.2
Der Algorithmenbegriff . . . . . . . . . . . . . . . . . . . . . . . . .
6
1.1.3
Problem, Algorithmus, Programm . . . . . . . . . . . . . . . . . . .
8
Information und Repräsentation
. . . . . . . . . . . . . . . . . . . . . . . . 14
1.2.1
Semantikschemata . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.2.2
Nachrichtenverarbeitung . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.2.3
Kodierung und Verschlüsselung . . . . . . . . . . . . . . . . . . . . . 22
Struktur und Invarianz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
1.3.1
Induktives Definieren . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
1.3.2
Induktives Beweisen . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
1.3.3
Anwendung: Semantik Boolescher Terme . . . . . . . . . . . . . . . . 34
2 WHILE–Programme und ihre Semantik
44
2.1
Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
2.2
Syntax von WHILE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
2.3
Semantik von WHILE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
2.3.1
Semantik von arithmetischen und Booleschen Ausdrücken . . . . . . 48
i
INHALTSVERZEICHNIS
1
2.3.2
Operationelle Semantik . . . . . . . . . . . . . . . . . . . . . . . . . 54
2.3.3
Denotationelle Semantik . . . . . . . . . . . . . . . . . . . . . . . . . 61
2.3.4
Axiomatische Semantik . . . . . . . . . . . . . . . . . . . . . . . . . 81
Kapitel 1
Einführung
1.1
Was ist Informatik?
Informatik ist die Wissenschaft von der Informationsverarbeitung.“
”
Auffallend ist, daß in dieser Definition das Wort Computer nicht vorkommt. Besser als
in der englischen Bezeichnung “Computer Science” wird hier der allgemeine Charakter
des Fachgebietes klar: Im Mittelpunkt der Informatik steht nicht der Umgang mit dem
Computer, sondern allgemeine Prinzipien und Vorgehensweisen zur Beherrschung komplexer Vorgänge und Systeme. Auf die Programmierung bezogen bedeutet das, daß weniger
das eigentliche Programmierhandwerk und die Fertigkeit der Rechnerbenutzung im Vordergrund stehen, sondern eher prinzipielle Fragen wie:
• wo liegen die Grenzen des (automatisch) Berechenbaren? Tatsächlich lassen sich
einige Probleme gar nicht mit Hilfe eines automatischen Verfahrens lösen, andere
wieder erfordern einen sehr hohen Mindestaufwand (eine sehr hohe Komplexität)
bei der Berechnung.
• wie muß man vorgehen, um auch sehr komplexe Probleme zuverlässig behandeln
zu können? Die Gebiete Software–Engineering und Software–Projektmanagement beschäftigen sich mit der Organisation umfangreicher Programmpakete und
das Gebiet Datenbanken sowie Teilgebiete der K ünstlichen Intelligenz mit der
Organisation großer Datenmengen.
• warum ist eine Programmier–, Modellierungs– oder Spezifikationssprache so und
nicht anders entworfen worden (formales Sprachdesign)?
• welche Maschinenarchitekturen sind zur Realisierung spezieller Problemklassen am
besten geeignet? Hier werden z.B. abstrakte Maschinen– und Berechnungs2
1.1. WAS IST INFORMATIK?
3
modelle studiert, um deren Anwendungsprofile zu ermitteln. Besonders interessant
ist hier die Untersuchung verteilter Systeme und paralleler Algorithmen.
• was sind die wesentlichen Charakteristika eines Problems im Hinblick auf eine abstrakte Modellierung (Problemanalyse, Requirements Engineering)?
• wann läßt sich ein gegebenes Verfahren einsetzen (Anwendungsprofile)?
• wer sind die zukünftigen Interessenten und Anwender (Relevanzanalyse)?
Bemerkung: Fragenstellen besitzt einen sehr hohen Stellenwert in der Wissenschaft allgemein, nicht nur in der Informatik. Oft liegt das zentrale Problem mehr in der richtigen
Fragestellung, als in der späteren Beantwortung.
Typisch für die Informatik ist also die Einbeziehung der Meta–Ebene“. Weniger das
”
spezielle Anwendungsprogramm ist Untersuchungsgegenstand, sondern das Design des
Gesamtkontextes steht im Vordergrund, in dem ein Problem behandelt oder Rechner und
Software entwickelt werden sollen. Der Informatiker schafft sich gewissermaßen die Welt
selbst, in der er arbeitet. Natürlich werden auch auf dieser Meta–Ebene Rechner zur Unterstützung eingesetzt, sowohl zur Analyse der derzeitigen ‘Programmierwelt’, als auch
als deren Bestandteile. Stichworte sind hier CASE (= Computer Aided Software–
Engineering) und CAD (= Computer Aided Design). In speziellen Bereichen gibt es
bereits Programmgeneratoren, die aus formalen Beschreibungen automatisch lauffähigen
Programmcode erzeugen. Prominentes Beispiel hierfür sind die sogenannten Compiler–
Compiler, die eine formale Programmiersprachenbeschreibung in einen Compiler umsetzen.
Grundlagen dieses Vorgehens sind ursprünglich hauptsächlich in der Mathematik beheimatete Fachgebiete wie: Algebra, Logik, Statistik, Zahlentheorie, Operations
Research, Informationstheorie, Codierungstheorie, Modelltheorie, Kryptographie, Topologie, Geometrie, Kontrolltheorie, Kybernetik, die inzwischen ihrerseits
stark von der Informatik geprägt wurden.
Jedoch anders als z.B. in der Mathematik, wo vergleichsweise sparsame Formalismen verwendet werden, ist für die Informatik typisch, daß die betrachteten Strukturen oder Objekte (Formeln, Programme, Datenstrukturen, ...) konstruktiv aus recht umfangreichen
Kollektionen von Grundobjekten mit Hilfe zahlreicher Operatoren gebildet werden. Man
vergleiche z.B. die Struktur einer Gruppe oder eines Vektorraumes mit der einer Programmiersprache. Konsequenz dieses strukturellen Reichtums ist die zum Teil m ühsame und im Vergleich zur Mathematik unelegante Beweisführung bei der Analyse solcher
Strukturen. Typisch sind die konstruktive Struktur ausnutzende Induktionsbeweise, die
zum Teil ermüdende Fallunterscheidungen verlangen (strukturelle Induktion). Aber
auch hier kann die geeignete Strukturierung der Beweise selbst Erstaunliches bewirken.
4
KAPITEL 1. EINFÜHRUNG
Aber nicht nur das technische Vorgehen unterscheidet sich. Auch die grundsätzlichen
Fragestellungen sind wegen des ‘konstruktiven’ Ansatzes der Informatik unterschiedlich.
Während der Mathematiker mit der Existenz und ggf. Eindeutigkeit gewisser Objekte
zufrieden sein mag, ist der Informatiker erst glücklich, wenn er das gewünschte Objekt
effektiv konstruieren kann.
Neben der Komplexität der betrachteten Strukturen ist vor allem auch die Flexibilität,
mit der die betrachteten Objekte modifiziert oder ausgetauscht werden, für die Informatik
charakteristisch. Die rasche Entwicklung neuer Technologien und Konzepte und die dadurch auftretenden neuen Probleme verlangen die Bereitschaft zu schnellem Umdenken.
Der oben erwähnte Aufbau des Gesamtkontextes muß ständig neu überdacht, ausgebessert
oder gar erneuert werden. Damit finden sich in der Informatik Charakteristika verschiedener klassischer Disziplinen:
• der pragmatische, ja zum Teil experimentelle Ansatz beim Design des Kontextes hat
ingenieurwissenschaftlichen Charakter,
• die formale Analyse der (komplexen) Strukturen und Phänomene ist mathematisch
und
• die Betrachtung von Anwenderakzeptanz sowie die wissenschaftliche Auseinandersetzung mit Sprachstrukturen sind geisteswissenschaftlicher Natur.
Gemeinsam ist diesen Punkten die Auseinandersetzung mit abstrakten Strukturen (Programmstrukturen, Sprachstrukturen, Datenstrukturen, Rechnerstrukturen, ...),
die in der reinen Form weder geisteswissenschaftlich, noch naturwissenschaftlich oder ingenieurwissenschaftlich ist. Deshalb bezeichnet man die Informatik, wie auch die Mathematik, als eine Strukturwissenschaft.
Die Informatik selbst wird üblicherweise in drei Bereiche eingeteilt (siehe Abbildung 1.1).
Informatik
KernInformatik
Praktische
Informatik
Technische
Informatik
Abbildung 1.1: Die drei Bereiche der Informatik
1.1. WAS IST INFORMATIK?
1.1.1
5
Geschichtliches zur Computer–Entwicklung
zw. 1623 und 1818 erste mechanische Rechenmaschinen (u.a. von Schickard, Blaise Pascal, Gottfried W. Leibnitz, P. M. Hahn, C. Thomas)
um 1800 J. Jacquard entwickelt einen automatischen Webstuhl, der mittels Lochkarten
gesteuert wird.
1833 Charles Babbage plant einen programmgesteuerten Rechner.
1886 H. Hollerith entwickelt verschiedene elektrische Lochkartenmaschinen, z.B. Sortierer,
die u.a. für die Volkszählung eingesetzt wurden. Ihre Programmierung“ erfolgte durch
”
Stecktafeln.
1934,41 Konrad Zuse plant und baut die erste funktionsfähige, programmierbare Rechenmaschine (die Z3). Diese wurde mittels Lochstreifen programmiert (die bei Bedarf zu
Schleifen zusammengeklebt werden konnten). Multiplikationszeit: ca. 3 Sekunden.
1944 H. Aiken (Harvard–Universität, in Zusammenarbeit mit IBM) baut die MARK 1.
1946 die erste Rechenmaschine mit Elektronenröhren (die ENIAC). 18000 Elektronenröhren. Multiplikationszeit: 3 ms.
Danach beginnt eine stürmische Entwicklung, die man heutzutage in Generationen“ un”
terteilt:
1. Elektronenröhren dienen als Schaltelemente.
2. Generation (ab 1957 bis Ende der 60er Jahre) Transistoren als Schaltelemente.
3. Generation (ab Mitte der 60er Jahre) Mikromodultechnik mit ca. 20 Schaltelementen pro cm2 .
4. Generation (ab Anfang der 70er Jahre) hochintegrierte Schaltkreise.
5. Generation (seit Anfang der 80er Jahre) höchstintegrierte Schaltkreise (VLSI);
mehrere Prozessoren auf einem Chip.
6. Generation (90er Jahre) hochprogrammierbare Bausteine (FPGAs, Field Programmable Gate Arrays), Embedded Systems.
7. Generation (seit 2000) SoCs (Systems on a Chip), MEMSs (Micro-electro-mechanical
systems).
Parallel zu dieser technologischen Entwicklung verläuft zwangsläufig auch eine Entwicklung der Software (wobei dieser Aspekt immer mehr Bedeutung einnimmt).
6
KAPITEL 1. EINFÜHRUNG
1. Generation Programmierung in Maschinencode.
2. Generation Assemblersprachen und Entwicklung erster höherer“ Programmier”
sprachen wie FORTRAN, ALGOL, COBOL, LISP.
3. Generation Entwicklung von Betriebssystemen mit Dialogbetrieb, Datenbanken,
Methoden der strukturierten Programmierung, Programmiersprachen C, PASCAL,
MODULA.
4. Generation Verteilte Systeme, Rechnernetze, Kommunikationsfähigkeit, hochwertige Programmierumgebungen. Programmiersprachen Smalltalk, C++, Oberon, ADA.
5. Generation Wissensverarbeitung, automatisches Schlußfolgern, deduktive Datenbanken, Expertensysteme, Programmierspache PROLOG.
6. Generation Ereignisorientierte Programmierung, Internetprogrammierung, Integrationsframeworks, Middleware, Programmierspachen JAVA, Delphi, Erlang.
Generelle Tendenz
Bei einzelnen Systemen verschwinden die Unterschiede zwischen mechanischen und elektronischen Systemen (MEMSs), und ebenso zwischen Hardware und Software (HardwareSoftware Codesign, SoCs) zunehmend. Die Heterogenität der Systeme (Ubiquitous Computing, Networks on a Chip) ist ein prägendes Merkmal. Umfangreiche Netzwerke von
Systemen und Endgeräten (devices) sind zu beherrschen und zu programmieren (JINI
Projekt).
1.1.2
Der Algorithmenbegriff
Ein zentraler Begriff in der Informatik ist der des Algorithmus.
Ein Algorithmus ist eine Vorschrift, eine Anleitung zur Lösung einer Aufgabenstellung, die
so präzise formuliert ist (z.B. in einer formalisierten (Programmier–) Sprache), daß sie im
Prinzip mechanisch“ (per Computer) ausgeführt werden kann. Der Algorithmenbegriff
”
ist also unabhängig von der Existenz einer speziellen Programmiersprache (in der der
Algorithmus codiert sein kann) und einer speziellen Maschine (auf der der Algorithmus
ausgeführt werden kann).
Im Alltag begegnen einem viele Algorithmen (Kochrezepte, Bedienungsanleitungen, die
Schulmethode zur Multiplikation zweier Zahlen, etc. ).
Da Algorithmen abstrakte Konzepte sind, die unabhängig von den technologischen Möglichkeiten existieren, ist es nicht verwunderlich, daß es Algorithmen schon im Altertum
1.1. WAS IST INFORMATIK?
7
gab. Bereits Euklids Buch Elemente“ (3. Jahrhundert v. Chr.) kann als eine Sammlung
”
von Algorithmen aufgefaßt werden. Der sog. Euklidsche Algorithmus wird heute noch
in der Computer–Zahlentheorie und Kryptographie verwendet. Er ist nach wie vor der
effizienteste Algorithmus zur Bestimmung des größten gemeinsamen Teilers zweier Zahlen
(siehe späterer Abschnitt).
Das Wort Algorithmus ist übrigens von dem Namen des Mathematikers Ibn Musa
Al–Khowarizmi abgeleitet, der um 800 in Bagdad bei dem Kalifen Harun Al Raschid
wirkte und verschiedene Bücher über die algorithmische“ Behandlung algebraischer“
”
”
Gleichungen schrieb.
Erst seit ca. 1930 (also vor der Computerzeit) gelang es, eine formal exakte Theorie der
Algorithmen und der algorithmischen Berechenbarkeit aufzustellen. In diesem Zusammenhang stellte sich überraschenderweise heraus, daß gewisse Probleme prinzipiell nicht algorithmisierbar sind. Insbesondere war es der G ödelsche Unvollständigkeitssatz, der
damals großes Aufsehen erregte (auch heute noch: vgl. das Buch Gödel, Escher, Bach“
”
von D. Hofstadter). Der Satz besagt (unter anderem), daß die Arithmetik, also die elementare Zahlentheorie, nicht algorithmisierbar ist. Das bedeutet, daß wir nur in Einzelfällen
die Richtigkeit oder Falschheit von arithmetischen Formeln, wie etwa
∀ x ∀ y. ((x + y) ∗ (x − y) = x ∗ x − y ∗ y),
bestätigen können. Es ist aber prinzipiell unmöglich, hierfür ein universelles Verfahren
anzugeben. Solch ein Verfahren würde für jede beliebige Formel nach endlicher Zeit entscheiden, ob diese Formel gültig ist oder nicht.
Als Urväter“ der Berechenbarkeitstheorie, und damit der Grundlagen der theoretischen
”
Informatik, wären zu nennen: K. Gödel, A. Church, S. Kleene, E. Post und insbesondere
A. M. Turing, dessen theoretische Arbeiten bis heute große Bedeutung haben (insbesondere seine Arbeit: On computable numbers, with an application to the Entscheidungs”
problem“).
Flußdiagramme
Eine Methode zur semi-formalen Darstellung von Algorithmen sind Flußdiagramme,
deren Syntax in Abbildung 1.2 beschrieben ist.
Bemerkung 1.1
Flußdiagramme sind unstrukturiert: Teilstrukturen wie WHILE–Schleifen sind dort
nicht explizit vorhanden, sondern müssen durch GOTO“-Anweisungen modelliert wer”
den. Das widerspricht der modernen Auffassung über Strukturiertheit von Programmen,
und reduziert die Anwendbarkeit der Flußdiagramme auf kleine Algorithmen. Um den
Vorteil graphischer Formalismen auch für größere Algorithmen anwenden zu können, muß
8
KAPITEL 1. EINFÜHRUNG
ja
Start
Stop
z=0
Wende
Formel (3) an
a==b ?
nein
a<b
Vergleiche
a und b
Einstiegs−
Ausstiegsstelle
elementare
Anweisung
a>b
Verzweigung
aufgrund einer
Bedingung
a=b
Lies x ein
Gib x aus
Eingabe,
Ausgabe
Abbildung 1.2: Flußdiagramme
Struktur, z.B. ein Hierarchiekonzept eingeführt werden. Solche Konzepte werden im Rahmen des Software–Engineering studiert. Beispiele hierfür sind Struktogramme und SADT–
Diagramme.
3
1.1.3
Problem, Algorithmus, Programm
Beim Übergang von einem (evtl. umgangssprachlich formulierten) Problem, über einen
abstrakten Algorithmus, der das Problem lösen soll, bis zu einem Programm, welches
dann auf einer Maschine ablaufen soll, müssen mehrere Phasen (oft auch wiederholt)
durchlaufen werden (vgl. auch Abbildung 1.3):
Vorgehen bei der Algorithmenentwicklung
1. Problemformulierung
2. Problemanalyse, Problemabstraktion und –formalisierung, Problemspezifikation
9
1.1. WAS IST INFORMATIK?
reale Welt
Problem
Abstraktion
abstrakte Objekte
Problemanalyse, funktionale
Spezifikation, Algorithmenentwurf
Algorithmus
Darstellung
informationsverarbeitendes
System
Darstellung in formalisierter Sprache
zur Ausführung durch Rechner
Programm
Realisierung
Umsetzung in Elementarschritte einer
Maschine
Maschine
Abbildung 1.3: Phasen der Programmentwicklung
3. Algorithmenentwurf
4. Korrektheitsnachweis, Verifikation
5. Aufwandsanalyse (Komplexitätsanalyse)
6. Programmkonstruktion
Wir führen diese Prinzipien vollständig an einem einfachen Beispiel vor. Hierbei soll
bewußt in Kauf genommen werden, daß einiges an Stoff vorweggenommen werden muß.
Es sollen einfach die unterschiedlichen Facetten der Informatik, von Theorie bis Praxis,
aufgezeigt werden.
Beispiel:
1. Problemformulierung
Suche in einer aufsteigend sortierten Liste mit n natürlichen Zahlen die Zahl x.
2. (a) Problemanalyse
Zu untersuchende Aspekte sind hier z.B.:
• Gibt es überhaupt eine (eindeutige) Lösung?
• Falls ja, wie kann die Lösung aussehen?
• Hat das Problem eine inhärente Struktur? Kann man es in (einfachere)
Teilprobleme zerlegen?
10
KAPITEL 1. EINFÜHRUNG
(b) Problemabstraktion und –formalisierung
Was ist Liste a mit n natürlichen Zahlen“?
”
=⇒ n–stelliges Tupel mit Zahlen a1 ,a2 ,...,an ,
d.h.: a =df (a1 , a2 , ..., an ).
Was heißt aufsteigend sortiert“?
”
=⇒ a1 ≤ a2 ≤ · · · ≤ an
Was heißt suche“?
”
=⇒ Überprüfen der Existenz eines Index m mit a m = x.
(c) Problemspezifikation
Eingabe: Liste natürlicher Zahlen a = (a1 , a2 , ..., an ) mit a1 ≤ a2 ≤ · · · ≤ an
und eine zu suchende Zahl x.
Ausgabe: Antwort zu der Frage, ob die Zahl x in a vorkommt. Falls die Antwort ja“ lautet, soll zusätzlich ein Index m mit am = x ausgegeben werden
”
(oft modifiziert man die Problemspezifikation derart, daß die Menge aller
oder bestimmter, z.B. der kleinste, Indizes m mit a m = x ausgegeben
werden soll).
3. Algorithmenentwurf
Dies entspricht der Ableitung eines Verfahrens aus der Problemspezifikation: Oft
gibt es verschiedene Alternativen. Erwünscht sind elegante, kurze, verständliche,
transparente, schnelle, leicht veränderbare Lösungen, wobei in der Regel nicht alle
Eigenschaften miteinander vereinbar sind.
Eine der möglichen Alternativen ist unter der Bezeichnung Bin äres Suchen bekannt. Wir wollen zunächst diese Lösung informell mit Hilfe eines Flußdiagramms
skizzieren (vgl. Abbildung 1.4).
Die Grundidee ist, ein beliebiges Element, zum Beispiel a m , auszuwählen und mit
dem Suchargument x zu vergleichen. Ist es gleich x, so ist die Suche beendet. Ist
es kleiner als x, so folgern wir aus der Sortiertheit der Liste, daß alle Elemente mit
kleinerem Index ausgeschlossen werden können. Ist es aber größer als x, so sind
alle Elemente mit größerem Index auszuschließen. Diese Methode heißt Bin äres
Suchen, weil in jedem Schritt entweder die Elemente oberhalb oder unterhalb der
gewählten Stichprobe eliminiert werden können.
Wie im Punkt “Aufwandsanalyse” auf Seite 12 später ausgeführt wird, empfiehlt es
sich aus Effizienzgründen dabei das mittlere Element zu wählen. Anders als in der
informellen Algorithmenbeschreibung unten ist diese Wahl bereits in die Flussdiagrammdarstellung integriert.
Diese Formulierung suggeriert, welche Objekte (besser: Datenstrukturen) für die
Formalisierung benötigt werden. Beispiel: Wir verwenden zwei Indexvariablen L und
R, die das linke und das rechte Ende des Bereichs markieren, in dem das gesuchte
Element noch liegen könnte.
11
1.1. WAS IST INFORMATIK?
START
lies x ein
L=1
R=n
found=false
L<=R
&&
found==false
nein
gib found aus
gib Index m aus
?
ja
END
m=(L+R) / 2
a[m]==x ?
ja
found=true
nein
a[m]<x ?
ja
L=m+1
nein
R=m−1
Abbildung 1.4: Flußdiagramm zum Binären Suchen
Der Algorithmus BinSuch läßt sich auch in einer an die Umgangssprache angelehnten
Notation formulieren.
(a) Setze L = 1, R = n und found = FALSE.
(b) (∗ Suchlauf ∗)1
Solange L ≤ R und found == FALSE gilt, wiederhole:
1
Durch (∗ Kommentar“ ∗) werden in Algorithmen Kommentare gekennzeichnet.
”
12
KAPITEL 1. EINFÜHRUNG
Sei L ≤ m ≤ R beliebig;
falls am == x setze found = TRUE;
ansonsten falls am < x setze L = m + 1;
sonst (∗ am > x ∗) setze R = m − 1
(c) Ergebnis wird mitgeteilt durch found.
Bemerkung 1.2
Der aufmerksame Leser wird sich fragen, warum wir das Problem derartig kompli”
ziert“ lösen. Es würde doch genügen, die Liste von links nach rechts zu durchsuchen
und zu stoppen, falls das momentan betrachtete Element mit x übereinstimmt oder
das Listenende erreicht wurde. Damit könnte man sogar auf die Sortiertheit der betrachteten Liste verzichten. Der Grund unserer Auswahl liegt aber in der Effizienz
des Algorithmus (vgl. Abschnitt 5).
3
4. Korrektheitsnachweis, Verifikation
Ein Programm heißt korrekt, wenn es die durch die Spezifikation geforderten Eigenschaften erfüllt. Den (formalen) Nachweis der Korrektheit nennt man Verifikation.
Klassisch unterscheidet man zwei Erfüllbarkeitsbegriffe, die sich ausschließlich beim
Terminierungsverhalten unterscheiden:
• partielle Korrektheit:
Falls der Algorithmus terminiert, liefert er die geforderten Ergebnisse.
• totale Korrektheit:
Der Algorithmus terminiert und liefert die geforderten Ergebnisse.
( totale Korrektheit = partielle Korrektheit + Termination“)
”
In der Tat ist BinSuch im folgenden Sinne total korrekt:
Theorem 1.3
Der Algorithmus BinSuch terminiert und liefert das geforderte Ergebnis, d.h.:
found == true gilt bei Terminierung genau dann, wenn es einen Index m gibt mit
am = x.
3
5. Aufwandsanalyse (Komplexitätsanalyse)
Hierunter versteht man eine Analyse des Ressourcenbedarfs des Algorithmus bez üglich
eines gewählten Komplexitätsmaßes. Im programmiersprachlichen Kontext ist es
üblich, Laufzeit– (und gegebenenfalls Speicher–) Bedarf als Kriterien zu verwenden.
Die Laufzeit wird dabei durch die Anzahl der auszuführenden Elementaroperationen (Zuweisungen, Vergleiche, etc. ) und der Speicherbedarf durch das Maximum
der gleichzeitig relevanten elementaren Datenobjekte modelliert.
Im Rahmen unseres Beispiels wollen wir die Anzahl der Schleifendurchläufe zählen,
die notwendig sind, um den gesuchten Index m mit a m = x zu finden bzw. festzustellen, daß kein solcher Index existiert. Den Aufwand innerhalb der Schleife betrachten
wir der Einfachheit halber als konstant.
1.1. WAS IST INFORMATIK?
13
Die Wahl von m ist bei jedem Schleifendurchlauf zunächst willkürlich und unerheblich für das letztendliche Resultat. Sie beeinflußt jedoch die Effizienz des Algorithmus. Selbstverständlich ist es erstrebenswert, in jedem Schritt möglichst viele
Elemente zu eliminieren.
Die beste Wahl von m ist damit das Element in der Mitte der Liste, weil so auf jeden
Fall die Hälfte der verbliebenen Elemente ausgeschlossen wird.
Im günstigsten Fall (best case) finden wir das gesuchte Element beim ersten Schleifendurchlauf.
Im schlechtesten Fall (worst case) halbieren wir das betrachtete Intervall bei jedem
Schleifendurchlauf solange, bis die Schleife wegen L > R abbricht. Da das Intervall
anfangs n Elemente umfaßt – dies entspricht der Länge der Liste – und es bei jedem Schleifendurchlauf halbiert wird, ist dies höchstens log 2 (n)–mal möglich. Wir
benötigen also im worst case nicht mehr als blog 2 (n)c + 1–Schleifendurchläufe.
Bemerkung 1.4
Das vorgestellte Verfahren unterscheidet sich also in seiner Komplexität positiv vom
naiven linearen Durchsuchen der Liste, das offensichtlich einen zu n proportionalen
Zeitaufwand erfordert. Auf der anderen Seite erfordert es aber die Zusatzbedingung,
daß die eingegebene Liste sortiert ist. Es ist daher bei der Organisation von Listen oft vorteilhaft, deren Elemente stets sortiert zu halten. Als drastisches Beispiel
stelle man sich den Aufwand der Suche in einem Telefonbuch vor, das nicht alphabetisch sortiert ist! Diese Situation ist für die Komplexität von Programmen typisch:
Zusätzliche Eigenschaften (wie hier die Sortiertheit) erlauben die Entwicklung von
effizienteren (schnelleren) Algorithmen für die Problemlösung.
3
6. Programmkonstruktion
Dabei wird der Kontext gewechselt, indem man sich mit einer Implementierung
des Algorithmus beschäftigt. Die Fragestellung entspricht einer Anpassung bzw.
Ergänzung der Spezifikation und des Algorithmus an den Kontext der tatsächlichen
Kodierung in einer Programmiersprache. Typische Fragen sind:
• Welche Programmiersprache steht zur Verfügung? (Wahl einer bestimmten Programmiersprache)
• Wie kommen die Daten in den Rechner und wie wird das Ergebnis ausgegeben? (Festlegen der Ein/Ausgabe–Medien, die die Ein/Ausgabe–Portion des
Programms bestimmen)
• Wie wird das Programm gegliedert? (Strukturierung, Überlegung einer möglichst sinnvollen, sparsamen, lesbaren, anschaulichen, internen Organisation des
Programms)
Für diese Vorlesung wird Java als Programmiersprache gewählt. Im entsprechenden
Kapitel wird auf die oben gestellten Fragen detailliert eingegangen. Hier wird lediglich der Algorithmus BinSuch in an Java angelehnter Notation angegeben. Dabei
14
KAPITEL 1. EINFÜHRUNG
ist noch anzumerken, daß die Definition des Wertebereiches eines Objekts durch die
Spezifikation seines Typs erfolgt. Seien also L, R und m vom Typ int (Dieser entspricht
in Java den ganzen Zahlen.), a ein Array vom Typ int mit [1..n] (Dies entspricht einem n-stelligen Tupel ganzer Zahlen.) und found vom Typ boolean (Dieser besteht
aus den Wahrheitswerten true und false.) und a[i] die i-te Komponente der Liste
a. Dann sieht eine Java Implementierung des Algorithmus zur Binären Suche wie
folgt aus:
L = 1; R = n; found = false
while ((L <= R) && (found == false)) {
m = (L+R)/ 2
/* L <= m <= R */
if (a[m] == x) {
found = true
} else {
if (a[m] < x) {
L = m+1
} else {
R = m-1
}
}
}
1.2
Information und Repräsentation
Um über die reale Welt Aussagen treffen und Zusammenhänge darstellen zu können, werden Begriffe benötigt, mit denen Objekte oder Erscheinungen (Sinneseindrücke, Wahrnehmungen) beschrieben werden. Jeder Begriff beschreibt dabei einen bestimmten Ausschnitt (ein Objekt bzw. einen Zusammenhang/Eindruck unter vielen) der Realität. Begriffe können aus anderen Begriffen zusammengesetzt sein. Dieses Vorgehen ist ein systematischer Aufbau eines Begriffs durch Komposition von Begriffen.
Beim Vorgang der Begriffsbildung wird abstrahiert. So enthält der Begriff Auto“
”
keine Information über Form, Farbe etc. der zugehörigen Objekte. Bei Ausführung der
gedanklichen Zuordnung des Begriffs Auto“ zu einem speziellen Objekt wird also von
”
individuellen Merkmalen abstrahiert. Das ist der Übergang von einem konkreten Objekt zu
seiner ‘Idee’ im platonischen Sinne. Der zur Begriffsbildung inverse Vorgang der Zuordnung
von Objekten zu einem Begriff ist das Verstehen oder Begreifen.
Unter Information verstehen wir den abstrakten Bedeutungsgehalt eines Begriffs. Damit
wir Information kommunizieren können, wird eine schematische, formalisierte Darstellung
1.2. INFORMATION UND REPRÄSENTATION
15
benötigt: die Repräsentation. Diese kann z.B. akustischer oder visueller Natur sein. Für
unser Auto wären die Zeichenreihen Auto“ oder voiture“, die entsprechenden Lautfolgen
”
”
(kontinuierliche akustische Darstellung), eine Folge von Morsesignalen (diskrete akustische
Signale) oder eine Piktogramm–Darstellung denkbar. Ein solcher Repräsentant (Träger)
einer Information heißt Nachricht oder auch Datum.
Beispiel 1.5 (Visuelle Repräsentationen der Zahl drei“)
”
• Dezimal: 3
• Binär: 11
• Unär: |||
3
Darstellung und Interpretation bestimmen, abstrakt gesehen, die Übergänge von der
Information zur Repräsentation und zurück. Wie schon gesehen, kann eine Information auf
verschiedene Weisen repräsentiert werden. Die Festlegung oder Konzeption eines geeigneten Repräsentationssystems ( Sprache“) zusammen mit einer adäquaten Begriffsbildung
”
ist eine zentrale Aufgabe der Informatik, die wir als Definition eines Semantikschemas
bezeichnen wollen.
Die Interpretation (Deutung) liefert zu jeder Repräsentation ihre Semantik (Bedeutung).
Ohne Interpretation sind alle Repräsentationen bedeutungsleer. Erst die Zuordnung von
Bedeutungen macht die Repräsentation zum Informationsträger.
Repräsentationen können unterschiedlich interpretiert werden. Zum Beispiel kann X“ ein
”
Buchstabe oder eine Darstellung der Zahl 10 sein, Mutter“ ein Elternteil oder das Ge”
genstück zur Schraube, und auch der englische Satz Fruit flies like a banana“ läßt sich
”
auf zweierlei Weise interpretieren. In vielen Fällen erlaubt der Kontext eine eindeutige
Interpretation. Aber auch das ist zumindest im umgangssprachlichen Bereich nicht garantiert. Im Gegensatz dazu wird im programmiersprachlichen Bereich großer Wert auf
eine eindeutige Interpretierbarkeit gelegt. Wie anders könnte man sonst die Zuverläßigkeit
von Softwaresystemen gewährleisten. In der Tat ist es hier aus Effizienzgründen im allgemeinen ungenügend, kontextabhängig eindeutig zu sein. Alles das ist beim Design von
Programmiersprachen zu beachten.
Im täglichen Leben wird zwischen Repräsentation und Information (als Resultat einer
Standardinterpretation) in der Regel nicht unterschieden. In der Informatik gibt es
a priori keine Standardinterpretation. Das erhöht den Spielraum beim Design der Semantikschemata, macht aber eine explizite begriffliche Trennung zwischen dem abstrakten
Informationsgehalt und der äußeren Form unbedingt notwendig. Wir haben es also hier
mit der in Abbildung 1.5 illustrierten Situation zu tun.
16
KAPITEL 1. EINFÜHRUNG
Objekte der realen Welt
Begriffsbildung
Verstehen
Abstraktion
Begreifen
Begriffe, Information
Darstellung
Interpretation
Repräsentationen
Abbildung 1.5: Abstraktionsebenen
Beispiel 1.6
Zur Verdeutlichung der verschiedenen Abstraktionsebenen ist in Abbildung 1.6 eine spezielle Instanz des Schemas aus Abbildung 1.5 gegeben.
3
ein Apfel
Abstraktion
(nicht alle Äpfel
sind gleich)
Verstehen
(sich einen Apfel vorstellen)
Begriffe: Apfel, pomme, apple
Darstellung
Interpretation
Repräsentation
"A" "p" "f" "e" "l"
Abbildung 1.6: Eine Beispielinstanz
1.2. INFORMATION UND REPRÄSENTATION
1.2.1
17
Semantikschemata
Hier soll der Zusammenhang zwischen Information und Repräsentation formal erfaßt werden. Dazu führen wir den Begriff des Semantikschemas ein.
Definition 1.7 (Semantikschema)
Ein Semantikschema ist ein Tupel hR, I, [[ · ]]i mit
• R: Menge der Repräsentationen oder Nachrichten,
• I: Menge der Begriffe oder Informationen,
• [[ · ]] ⊆ R × I: Semantik(–Relation) oder Interpretation. Falls hr, ii ∈ [[ · ]] , so ist
i eine Information zur Repräsentation r. Im folgenden schreiben wir oft i ∈ [[ r ]]
anstelle von hi, ri ∈ [[ · ]]. Ist [[ r ]] einelementig, so schreiben wir auch i = [[ r ]].
3
Im folgenden wollen wir den neu eingeführten Begriff anhand einer Reihe von Semantikschemata für die Menge der natürlichen Zahlen illustrieren. Dabei sollen gleichzeitig auch
die später definierten Eigenschaften von Semantikschemata motiviert werden.
Beispiel 1.8 (Unärdarstellung natürlicher Zahlen)
Eine Interpretation der Unärdarstellung von natürlichen Zahlen ist durch das folgende
Semantikschema hRu , I u , [[ · ]]u i gegeben mit:
• Ru =df {|, ||, |||, . . . },
• I u =df N =df {1, 2, . . . }: Natürliche Zahlen (als Begriffe bzw. Information, nicht
als ihre Darstellung im Dezimalsystem! Siehe auch nächstes Beispiel) und
• [[ · ]]u =df {h|| . . . |, ki | k ∈ N}. Die Semantik einer Nachricht aus k Strichen ist also
| {z }
k
die natürliche Zahl k.
3
Beispiel 1.9 (Dezimaldarstellung nat ürlichen Zahlen)
Das folgende Semantikschema hR d , I d , [[ · ]]d i für die Dezimaldarstellung natürlichen Zahlen
verdeutlicht noch einmal den Unterschied zwischen der Information und ihrer Repräsentation:
• Rd =df {(di )0≤i≤n | di ∈ {0, . . . , 9} f.a. 0 ≤ i ≤ n ∧ n ∈ N0 }.
18
KAPITEL 1. EINFÜHRUNG
• I d =df N0 =df {0, 1, 2, . . . }: Natürliche Zahlen (als Begriffe bzw. Informationen,
nicht als ihre Notation im Dezimalsystem!) und
• [[ · ]]d =df {h(di )0≤i≤n ,
n
P
di ∗ 10n−i i | n ∈ N0 } = {(0, 0), (1, 1), (2, 2), . . .}.
i=0
3
Repräsentation und Information werden oft gleich geschrieben. Dies bedeutet aber nicht,
daß auf die Unterscheidung verzichtet wird. Ob nun die Zahl oder ihre Darstellung gemeint
ist, muß dann aus dem Kontext geschlossen werden.
Beispiel 1.10 (Binärdarstellung natürlicher Zahlen)
Das folgende Semantikschema hR b , I b , [[ · ]]b i arbeitet auf der Binärdarstellung der natürlichen Zahlen.
• Rb = {(di )0≤i≤n | di ∈ {0, 1} f.a. 0 ≤ i ≤ n ∧ n ∈ N0 }.
• I b =df N0 =df {0, 1, 2, . . . }: Natürliche Zahlen (wieder als Begriffe bzw. Informationen,
nicht als ihre Notation im Dezimalsystem).
• [[ · ]]b =df {h(di )0≤i≤n ,
n
P
di ∗ 2n−i i | n ∈ N0 } = {(0, 0), (1, 1), (10, 2), . . .}.
i=0
3
Beispiel 1.11 (p-adische Darstellung nat ürlichen Zahlen)
Als Verallgemeinerung der vorherigen Beispiele kann die p-adische Darstellung der nat ürlichen Zahlen betrachtet werden. Sei p Repräsentation der p-ten natürlichen Zahl (p ≥ 1).
Dann erhalten wir folgendes Semantikschema hR p , I p , [[ · ]]p i:
• Rp =df {(di )0≤i≤n | di ∈ {0, . . . , p-1} f.a. 0 ≤ i ≤ n ∧ n ∈ N0 }.
• I p =df N0 = {0, 1, 2, . . . }: Natürliche Zahlen (wieder als Begriffe bzw. Informationen,
nicht als ihre Notation im Dezimalsystem).
• [[ · ]]p =df {h(di )0≤i≤n ,
n
P
di ∗ pn−i i | n ∈ N0 }.
i=0
Von besonderer Bedeutung ist hier die 16-adische Darstellung, auch Hexadezimaldarstellung genannt, bei der üblicherweise die Buchstaben A bis F anstelle der Ziffernreps̈entationen
für 10 bis 15 treten. Wir bezeichnen diese mit hR h , I h , [[ · ]]h i.
3
Beispiel 1.12 (Semantikschema für Kardinalzahlen in MODULA–2)
Bei den Kardinalzahlen von MODULA–2 handelt es sich um einen beschränkten Bereich
der natürlichen Zahlen inklusive der Null. Sie werden intern als eine Bitfolge der Länge
1.2. INFORMATION UND REPRÄSENTATION
19
B (normalerweise B = 16 oder B = 32) abgelegt (vgl. Beispiel 7), so daß nur Zahlen aus
dem Bereich {0, 1, . . . , 2B − 1} abgelegt werden können. Das zugehörige Semantikschema
hRK , I K , [[ · ]]K i hat folgende Form:
• RK =df {0, 1, . . . , 2B -1},
• I K =df N0 = {0, 1, . . .} und
• [[ · ]]K =df {hk, ki | k ∈ RK }.
3
Beispiel 1.13 (Vervollständigte Kardinalzahlen)
Zahlen außerhalb des im vorigen Beispiel dargestellten Bereichs können intern nicht abgespeichert werden. Treten sie als Rechenergebnis auf, so kommt es zu einem Unter–
bzw. Überlauf. Dies führt meist zu einem Fehlerabbruch der Berechnung und wird durch
folgendes Semantikschema hRuo , I uo , [[ · ]]uo i mit
• Ruo =df Rk ∪ {underflow, overflow},
• I uo =df
Z und
•
[[ r ]]uo =df
modelliert.


falls r = underflow
 {i ∈ Z | i < 0}
{i ∈ Z | i > 2B − 1} falls r = overflow


[[ r ]]K
sonst
3
Das Beispiel zeigt, daß eine Repräsentation, wie hier zum Beispiel “underflow”, durchaus uneindeutig, das heißt durch mehrere Informationen interpretiert werden kann. Im
folgenden werden einige zentrale Eigenschaften von Semantikschemata definiert.
Definition 1.14
Ein Semantikschema hR, I, [[ · ]]i heißt
• total, falls ∀ r ∈ R. [[ r ]] 6= ∅.
• eindeutig gdw. [[ · ]] eine totale Funktion ist, d.h. falls
∀ r ∈ R. |{i | hr, ii ∈ [[ · ]]}| = 1.
Ist [[ · ]] eindeutig, so heisst [[ · ]]
20
KAPITEL 1. EINFÜHRUNG
• injektiv gdw. [[ · ]] injektiv ist, d.h. falls
∀ r1 , r2 ∈ R. [[ r1 ]] = [[ r2 ]] ⇒ r1 = r2 .
• surjektiv gdw. [[ · ]] surjektiv ist, d.h. falls
∀ i ∈ I. ∃ r ∈ R. [[ r ]] = i
3
Alle Semantikschemata aus den Beispielen 1.8 bis 1.13 sind total, da jeder Repräsentation
mindestens eine Information zugeordnet wird. Würde man allerdings beispielsweise in
Beispiel 1.10 die Informationsmenge auf N beschränken, so wäre das Semantikschema
nicht mehr total, da es für die Repräsentation 0 keine entsprechende Information mehr
geben würde. Die Semantikschmata sind auch bis auf Beispiel 1.13 alle eindeutig, da jeder
Repräsentation genau eine Information zugeordnet wird, was im Falle von Beipiel 1.13 wie bereits dargelegt - nicht der Fall ist. Unter den eindeutigen Semantikschemata sind die
Unärdarstellung natürlicher Zahlen (Bsp. 1.8) und die MODULA-2 Kardianalzahlen (Bsp.
1.12) injektiv, denn es gibt keine Information, die verschiedene Repräsentationen besitzt.
Auf der anderen Seite ist z.B. das Semantikschema für die Binärdarstellung natürlicher
Zahlen (Bsp. 1.10) nicht injektiv. Dieses liegt daran, daß die Repräsentationen führende
Nullen zulassen. So wird z.B. sowohl 1 als auch 01 als natürliche Zahl 1 interpretiert.
Dieser “Defekt” ließe sich aber leicht beseitigen, indem die Repräsentationsmenge des
Semantikschemas eingeschränkt würde auf:
Rb =df {(di )0≤i≤n | di ∈ {0, 1} f.a. 0 ≤ i ≤ n ∧ n ∈ N0 ∧ ((d0 6= 0) ∨ (n = 0)) }.
Schließlich sind für unsere Beispiele alle eindeutigen Semantikschemata bis auf die MODULA-2 Kardinalzahlen (Bsp. 1.12) surjektiv, denn jede Information besitzt auch mindestens
eine zugehörige Repräsentation. Im Falle der MODULA-2 Kardianalzahlen ist dieses aber
für natürliche Zahlen größer als 2B − 1 nicht der Fall.
1.2.2
Nachrichtenverarbeitung
Formal gesehen ist eine Nachrichtenverarbeitung eine Abbildung, die eine Repräsentation
auf eine Repräsentation abbildet. Es handelt sich also um eine ‘Repräsentationstransformation’, die zunächst vollkommen interpretationsunabhängig ist.
Definition 1.15 (Äquivalenztransformation, Kongruenztransformation)
Gegeben seien zwei Semantikschemata hR 1 , I 1 , [[ · ]]1 i und hR2 , I 2 , [[ · ]]2 i. Dann heißt
eine Funktion f : R1 → R2 Nachrichtenverarbeitung. Abbildung 1.7 illustriert diese
Situation.
21
1.2. INFORMATION UND REPRÄSENTATION
R1
f
R2
[[ · ]]2
[[ · ]]1
I2
I1
Abbildung 1.7: Nachrichtenverarbeitung
• f heißt Kongruenztransformation, falls
∀ r, r 0 ∈ R1 . [[ r ]]1 = [[ r 0 ]]1 ⇒ [[ f (r) ]]2 = [[ f (r 0 ) ]]2
( Merke: Kongruenztransformationen erhalten die Bedeutungsgleichheit.“)
”
• Oft liegt wie in Abbildung 1.8 illustriert die Situation vor, daß I 1 = I 2 . In dieser
Situation heißt f Äquivalenztransformation, falls
∀ r ∈ R1 . [[ r ]]1 = [[ f (r) ]]2 .
( Merke: Äquivalenztransformationen erhalten die Bedeutung.“)
”
3
R1
f
R2
[[ · ]]2
[[ · ]]1
I1 = I2
Abbildung 1.8: Situation für Äquivalenztransformationen
Beispiel 1.16 (Nachrichtenverarbeitungen)
Ein Beispiel für eine Nachrichtenverarbeitung ist eine Konversion zwischen verschiedenen
Zahldarstellungen. Die Funktion f : R h → Rb , die definiert ist durch
¯ 0 ) · · · f(d
¯ n ),
f ((di )0≤i≤n ) = f(d
ordnet jeder Hexadezimalzahl ihre Binärzahldarstellung zu, wobei die Hilfsfunktion f¯ :
{0, . . . , F} → {0, 1}4 jeder Hexadezimalziffer ihre zugehörige Binärdarstellung zuordnet:
ḡ(0) = 0000, ḡ(1) = 0001, ḡ(2) = 0010, . . . , ḡ(F) = 1111.
22
KAPITEL 1. EINFÜHRUNG
Die Nachrichtenverarbeitung f ist sowohl eine Kongruenztransformation als auch eine
Äquivalenztransformation.
Die Funktion g : Rb → Ruo , die definiert ist durch
(
[[ x ]]b
falls 0 ≤ [[ x ]]b < 2B
g(x) =
overflow sonst
ist ein Beispiel einer Kongruenztransformation, die keine Äquivalenztransformation ist.
Letzteres ist zwar formal ohnehin durch die unterschiedlichen Informationsmengen ausgeschlossen. Diese Einschränkung ließe sich aber leicht beseitigen, indem man I b zu Z
erweitert. Selbst dann bleiben aber Information duch g nicht erhalten, denn es gilt beispielsweise für die Binärrepräsentation x von 2B :
2B = [[ x ]]b 6= [[ g(x) ]]b = [[ overflow ]]uo = {i ∈ Z | i > 2B − 1} = {2B , 2B+1 , . . .}
.
3
Es gilt jedoch folgender unmittelbarer Zusammenhang:
Lemma 1.17
Seien hR1 , I, [[ · ]]1 i und hR2 , I, [[ · ]]2 i Semantikschemata mit gleicher Informationsmenge
und f : R1 → R2 eine Nachrichtenverarbeitung. Dann gilt:
Falls f eine Äquivalenztransformation ist, so ist f auch eine Kongruenztransformation. 3
1.2.3
Kodierung und Verschlüsselung
Eine Äquivalenztransformation wird oft auch Kodierung genannt. Kodierungen verwendet man zum Beispiel zur Übertragung von Nachrichten durch binäre Signale (R2 =
{0, 1}∗ ) oder zur Fehlererkennung bei solchen Übertragungen. Dabei bezeichnet M ∗ für
eine Menge M die Menge der leeren oder nicht-leeren endlichen Folgen mit Elementen aus
M:
M ∗ = {m1 · · · mk | m1 , . . . , mk ∈ M, k ≥ 0}.
Elemente w ∈ M ∗ bezeichnet man auch als Worte über M . Das leere Wort (k = 0)
wird mit ε bezeichnet.
Eine Kodierung f heißt Verschlüsselung, wenn f (r) weit weg von der Standardinter”
pretation ist“, d.h. die Bedeutung [[ r ]] 1 durch f (r) verdeckt wird.
Beispiel 1.18 (Caesar Chiffre)
Hier wird ein zyklischer Shift der Buchstaben um 3 (Transposition, Permutation von Buchstaben) durchgeführt: A → D , B → E , . . . , Z → C.
3
1.3. STRUKTUR UND INVARIANZ
23
Eine andere Verschlüsselung ist das Rückwärtsschreiben.
Zeichen und Bedeutungen
Gerade in der Informatik ist es wichtig, immer genau zu wissen, wie Zeichen situationsgerecht interpretiert werden müssen. Besonders betrifft das die vielen Situationen, in denen
mit Varianten von Gleichheiten umgegangen wird. Die folgende Tabelle fasst einige zentrale Situationen zusammen und zeigt, wie wir die Unterschiede weitestgehend auch direkt
durch die Zeichen verdeutlichen werden. Tatsächlich kann man in der Literatur in all diesen
Situationen auch ein einfaches ’=’ finden.
=df
=
==
=
definierend: die linke Seite benennt das Objekt der rechten Seite
Zuweisung (Java-Syntax); entspricht “:=” etwa in Pascal
Wertegleichheit (Java-Syntax)
steht für syntaktische Gleichheit von Booleschen Termen und
arithmetischen Ausdrücken
Beachte aber die Existenz von Abkürzungen, z.B. x[t/x] = t
die semantische Gleichheit wird explizit ausgedrückt,
z.B. [[ 2x ]] = [[ x + x ]]
die Relation = in Formeln wird semantisch interpretiert,
z.B. x = 5 ∧ y = 10 (vgl. Hoare-Logik)
Es ist Teil des Lernziels, die richtige Interpretation der Gleichheit im
jeweiligen Kontext sicher zu beherrschen.
1.3
Struktur und Invarianz
Grundlegend für den Aufbau komplexer Objekte ist die systematische, hierarchische Konstruktion größerer Bausteine aus bereits ‘produzierten’ kleineren Bausteinen. Besonders
wichtig ist dieses Prinzip bei der Programmierung großer Softwaresysteme.
Voraussetzung für den Erfolg dieses Vorgehens ist, daß die Konstruktionsumgebung ein
hierarchisches Vorgehen entlang der inhärenten Problemstruktur unterstützt. Insbesondere
ist es erforderlich, daß die Repräsentationssprache des zugrundeliegenden Informationssystems selbst strukturiert ist, und zwar in einer Weise, daß Teilstrukturen der Repräsentation in ‘natürlicher’ Weise Teilstrukturen der Informationsebene entsprechen:
Repräsentations- und Informationsstruktur m üssen zueinander passen.
Formal bedeutet das, daß die Konstruktoren oder Operatoren, mit denen Bausteine zusammengesetzt werden können, Kongruenztranformationen induzieren. Diesen Aspekt wollen
24
KAPITEL 1. EINFÜHRUNG
wir später noch genauer betrachten.
Erste Voraussetzung strukturierten Vorgehens ist die induktive Definition der Repräsentationssprache.
1.3.1
Induktives Definieren
Die induktive Definition einer Menge erfolgt durch Angabe
1. einer Menge elementarer Grundobjekte oder atomarer Bausteine und
2. einer Menge von Operatoren, die es erlauben, kleinere Bausteine entsprechend einer
Konstruktionsvorschrift zu größeren Einheiten zusammenzusetzen.
Formal sind solche Operatoren Funktionen f einer jeweils vorgebenen Stelligkeit n (die
Anzahl der bei der Konstruktion mit Hilfe von f nötigen kleineren Bausteine). ‘Wohlkonstruierte’ neue Objekte haben dann die Gestalt f (x 1 , · · · , xn ), wobei die xi die verwendeten
kleineren Bausteine bezeichnen. Die induktiv definierte Menge besteht also exakt aus den
in endlich vielen Schritten ‘wohlkonstruierbaren’ Bausteinen.
Syntax Boolescher Terme
Definition 1.19 (Syntax)
Sei ID eine Menge von Identifikatoren (Variablen), z.B. ID = {x, y, z, ...}. Die Menge BT
aller Booleschen Terme über ID ist wie folgt induktiv definiert:
1. true, false und Identifikatoren id aus ID, sind Boolesche Terme. Sie werden auch
atomare“ Boolesche Terme oder kurz Atome genannt.
”
2. Sind t1 und t2 Boolesche Terme, so sind auch
• (¬t1 ), die Negation von t1 ,
• (t1 ∧ t2 ), die Konjunktion von t1 und t2 und
• (t1 ∨ t2 ), die Disjunktion von t1 und t2
Boolesche Terme.
3. Weitere als die aus 1) und 2) konstruierbaren Booleschen Terme gibt es nicht.
Ein Boolescher Term t1 , der als Baustein bei der Konstruktion eines Booleschen Terms t 2
auftritt, heißt Teilterm von t2 .
3
1.3. STRUKTUR UND INVARIANZ
25
Beispiel 1.20
(¬(t1 ∨ (false ∧ x))) ist ein Boolescher Term mit den folgenden Teiltermen:
t1 , false, x, (false ∧ x), (t1 ∨ (false ∧ x)), (¬(t1 ∨ (false ∧ x))).
3
In der Informatik hat sich ein spezielles Format zur induktiven Definition syntaktischer
Strukturen (Sprachen) durchgesetzt, die (erweiterte) Backus-Naur-Form (BNF).
Eine BNF besteht aus endlich vielen Regeln der Form
linke Seite ::= rechte Seite
Regeln werden im Zusammenhang mit BNFs oft auch als Produktionen bezeichnet. Die
linken“ und rechten Seiten“ basieren auf disjunkten Mengen T und N :
”
”
• T ist eine endliche Menge sogenannter Terminalsymbole. Terminalsymbole sind atomare syntaktische Symbole der Sprache, die durch die BNF beschrieben wird.
• N ist eine endliche Menge sogenannter Nichtterminalsymbole. Nichtterminalsymbole
dienen als Hilfsbezeichner.
Jede linke Regelseite besteht aus einem Nichtterminalsymbol A ∈ N . Rechte Regelseiten
sind leere oder nichtleere Folgen von Terminal- oder Nichtterminalsymbolen. In einer Regel
LS ::= RS gilt also LS ∈ N und RS ∈ (N ∪ T ) ∗ . Dabei ist
(N ∪ T )∗ =df {w1 · · · wk | w1 , . . . , wk ∈ (N ∪ T ), k ≥ 0} .
Insbesondere ist die leere Zeichenreihe ε als rechte Seite einer Regel zugelassen.
Konvention 1.21
Enthält eine BNF mehrere Regeln mit identischen linken Seiten, etwa
LS ::= RS 1 , LS ::= RS 2 , ..., LS ::= RS n
so schreiben wir für diese Regeln auch kurz
LS ::= RS 1 | RS 2 | ... | RS n
Der Konstruktor |“ ist also als Regelalternative“ zu verstehen.
”
”
3
26
KAPITEL 1. EINFÜHRUNG
Beispiel 1.22 (BNF für Boolesche Terme)
Die Booleschen Terme sind durch die folgende BNF mit T = df {true, false, ¬, ∧, ∨, (, )} ∪
ID und N =df {ABT , BT } charakterisiert (dabei sei id ∈ ID): 2
ABT
::= true | false | id
BT
::= ABT | (¬BT ) | (BT ∧ BT ) | (BT ∨ BT )
Alternativ kann man auch die folgende BNF mit nur einem Nichtterminalsymbol verwenden:
BT
::= true | false | id | (¬BT ) | (BT ∧ BT ) | (BT ∨ BT )
In diesem Beispiel nehmen wir an, daß ID, die Menge der Identifikatoren, endlich ist und
verstehen die Regel ABT ::= id (bzw. BT ::= id) als Abkürzung für je eine Regel ABT ::= x
(bzw. BT ::= x) für jeden Identifikator x ∈ ID.
3
Bemerkung 1.23 (BNF-artige Definitionen)
Oft arbeitet man auch mit sogenannten BNF-artigen Definitionen, in denen man auf die
Forderung nach Endlichkeit von N , T und der Menge der Regeln verzichtet. Fasst man das
Regelsystem aus Beispiel 1.22 als BNF-artige Definition auf, so wird dadurch die Menge
der Booleschen Terme auch definiert, wenn ID unendlich viele Identifikatoren enthält.
Die im folgenden definierten Begriffe übertragen sich in natürlicher Weise auf BNF-artige
Definitionen.
3
Eine BNF induziert eine Ableitungsrelation ⇒ ⊆ (N ∪T ) ∗ ×(N ∪T )∗ , die wie folgt definiert
ist:
w ⇒ w0
gdw.
es gibt w1 , w2 ∈ (N ∪ T )∗ und eine Regel A ::= r, so daß
w = w1 Aw2 und w0 = w1 rw2 .
In der beschriebenen Situation sagt man auch, dass w 0 aus w durch Anwenden der Regel
A ::= r abgeleitet werden kann. In einem Ableitungsschritt, in dem man eine Regel A ::= r
anwendet, ersetzt man also in einem Wort w ein Vorkommen von A durch r. Dabei bleibt
alles, was in w links oder rechts von dem ersetzten Vorkommen von A steht, unverändert.
Die oben angegebene Definition von ⇒ schreibt man auch kürzer als
w1 Aw2 ⇒ w1 rw2 ,
falls A ::= r eine Regel der BNF ist.
Eine Folge w1 , . . . , wk von Wörtern über N ∪ T (d.h. w1 , . . . , wk ∈ (N ∪ T )∗ ) heißt Ableitunsfolge, wenn wi ⇒ wi+1 für alle i ∈ {1, . . . , n−1} gilt. Man sagt, ein Wort w 0 ∈ (N ∪T )∗
lasse sich aus einem Wort w ∈ (N ∪ T )∗ ableiten, wenn es eine Ableitungsfolge w 1 , . . . , wk
mit w = w1 und w0 = wk gibt. In diesem Fall schreibt man auch w ⇒ ∗ w0 . Für ein gegebenes Nichtterminalsymbol A besteht die von A generierte Sprache aus genau den Wörtern
2
Dabei stehen intuitiv die Bezeichner ABT für atomare Boolesche Terme und BT für Boolesche Terme.
27
1.3. STRUKTUR UND INVARIANZ
w ∈ T ∗ , die sich aus A ableiten lassen. Die von A generierte Sprache bezeichnet man als
L(A). Es gilt also:
L(A) =df {w ∈ T ∗ | A ⇒∗ w} .
Beachte, daß die Wörter in der von A abgeleiteten Sprachen keine Nichtterminalsymbole
enthalten dürfen.
Beispiel 1.24 (Ableitungsfolge, generierte Sprache)
Als Beispiel geben wir zur BNF aus Beispiel 1.22 eine Ableitungsfolge für das Wort ((¬x)∧
(true ∨ y)) an:
BT ⇒ (BT ∧ BT )
⇒ (BT ∧ (BT ∨ BT ))
⇒ (BT ∧ (true ∨ BT ))
⇒ ((¬BT ) ∧ (true ∨ BT ))
⇒ ((¬BT ) ∧ (true ∨ y))
⇒ ((¬x) ∧ (true ∨ y))
Diese Ableitunsfolge zeigt, daß ((¬x) ∧ (true ∨ y)) zur von BT generierten Sprache L(BT )
gehört. Beachte aber, daß das Wort (BT ∧ BT ) nicht zur von BT generierten Sprache
gehört, da es Nichtterminalsymbole enthält.
3
Für den in Beispiel 1.22 abgeleiteten Booleschen Term gibt es weitere Ableitungsfolgen,
in denen unabhängige Teilterme in anderer Reihenfolge abgeleitet werden, z.B.:
BT
⇒
⇒
⇒
⇒
⇒
⇒
(BT ∧ BT )
((¬BT ) ∧ BT )
((¬x) ∧ BT )
((¬x) ∧ (BT ∨ BT ))
((¬x) ∧ (true ∨ BT ))
((¬x) ∧ (true ∨ y))
Die Essenz aller dieser Ableitungsfolgen kommt im sogenannten Ableitungsbaum zum Ausdruck, der für alle diese Ableitungsfolgen gleich ist:
(
(
¬
x
)
∧
BT
BT
(
true
∨
BT
BT
y
)
)
BT
BT
Genauer ist ein Ableitungsbaum ein geordneter Baum (siehe Vorlesung “Datenstrukturen,
Algorithmen und Programmierung 2”), dessen Blätter mit Terminal- oder Nichtterminalsymbolen und dessen innere Knoten mit Nichtterminalsymbolen benannt sind und f ür den
für jeden Knoten v gilt:
28
KAPITEL 1. EINFÜHRUNG
Ist v mit dem Nichtterminalsymbol A benannt und seine Söhne von links nach
rechts mit den Symbolen s1 , . . . , sk ∈ (N ∪ T ) so ist A ::= s1 · · · sk eine Regel
der BNF.
Insbesondere kann also ein Blatt des Baumes nur dann mit einem Nichtterminalsymbol A
benannt sein, wenn es in der BNF eine Regel A ::= ε gibt.
Man kann zeigen, dass ein Wort w ∈ T ∗ genau dann in der von A generierten Sprache
liegt, wenn es einen Ableitungsbaum gibt, dessen Wurzel mit A benannt ist und an dessen
mit Terminalsymbolen benannten Blättern w steht, wenn man sie von links nach rechts
liest.
Eng verwandt mit dem Begriff BNF ist der Begriff kontextfreier Grammatiken. Eine kontextfreie Grammatik ist ein Quadrupel G = (N , T , S, P ), wobei
• N eine endliche Menge von Nichtterminalsymbolen,
• T eine endliche Menge von Terminalsymbolen,
• S ∈ N ein ausgezeichnetes Nichtterminalsymbol, das sogenannte Startsymbol, und
• P ⊆ N ×(N ∪T )∗ eine endliche Menge von Regeln (Produktionen) ist. Dabei schreibt
man wie bei einer BNF eine Regel aus P oft in der Form LS ::= RS.
Eine kontextfreie Grammatik besteht also aus der durch N , T und P gegebenen BNF
zusammen mit einem Startsymbol S ∈ N . Die Sprache von G ist die Sprache, die vom
Startsymbol S generiert wird. Sie wird mir L(G) bezeichnet.
Beispiel 1.25 (Kontextfreie Grammatik f ür Boolesche Terme)
Wähle N , T so wie in Beispiel 1.22 und wähle P als die Menge der in Beispiel 1.22
angegebenen Regeln. Dann ist die Sprache der Grammatik G = (N , T , BT , P ) die Menge
der Booleschen Terme. Dabei nehmen wir wieder an, daß ID endlich ist.
3
Bemerkung 1.23 gilt analog auch für kontextfreie Grammatiken und führt dann zum Begriff
der kontextfrei-artigen Grammatiken.
1.3.2
Induktives Beweisen
Induktives Beweisen bedeutet “Beweisen entlang der induktiven Struktur” der zugrundeliegenden Objekte. Klassisch sind Beweise durch vollst ändige Induktion, die der induktiven Struktur der natürlichen Zahlen folgen:
1.3. STRUKTUR UND INVARIANZ
29
Jede natürliche Zahl entsteht durch eine endliche Anwendung der Nachfolgerfunktion ‘+ 1’.
Formale Grundlage hierfür sind die Peano-Axiome:
Axiomatische Begründung: die Peano-Axiome
P1 1 ist eine natürliche Zahl: 1 ∈ N.
P2 Jede natürliche Zahl besitzt eine eindeutig bestimmte natürliche Zahl als Nachfolger:
∀n ∈ N ∃m ∈ N. m = succ(n) ∧ (∀m0 ∈ N. m0 = succ(n) ⇒ m = m0 )
P3 1 ist nicht Nachfolger einer natürlichen Zahl:
¬∃n ∈ N. 1 = succ(n)
P4 Verschiedene natürliche Zahlen haben verschiedene Nachfolger:
∀m, n ∈ N. n 6= m ⇒ succ(n) 6= succ(m)
P5 Axiom der vollständigen Induktion:
Ist eine Aussage über natürliche Zahlen für 1 wahr und läßt sich ihre Gültigkeit
für jede größere natürliche Zahl aus der Gültigkeit der Aussage für ihren Vorgänger
ableiten, dann ist sie für jede natürliche Zahl wahr.
⇒ ∀ n ∈ N. A(n) .
A(1) ∧ ∀ n ∈ N. A(n) ⇒ A(n + 1)
Bemerkung: Induktionsbeweise erlauben es, Eigenschaften für unendlich viele Objekte,
z.B. die natürlichen Zahlen, in einheitlicher Weise zu verifizieren.
Im folgenden betrachten wir einige typische Beispiele für Eigenschaften, die am besten mit
vollständiger Induktion bewiesen werden. Für alle n ∈ N gilt:
1.
n
P
i=1
2.
n
P
i=
n∗(n+1)
,
2
Summe der ersten n natürlichen Zahlen.
(2i − 1) = n2 , Summe der ersten n ungeraden Zahlen.
i=1
3.
n
P
2i = n ∗ (n + 1), Summe der ersten n geraden Zahlen.
i=1
4.
n
P
i2 =
n∗(n+1)∗(2n+1)
,
6
i3 =
n2 ∗(n+1)2
,
4
i=1
5.
n
P
i=1
Summe der ersten n Quadratzahlen.
Summe der ersten n Kubikzahlen.
30
KAPITEL 1. EINFÜHRUNG
6. Es gibt n! Permutationen über n Objekten.
7. Es gibt 2n Teilmengen von n–elementigen Mengen.
8. Es gibt 2n Binärworte der Länge n.
Das Beweisprinzip der vollständigen Induktion, wie wir es bisher kennengelernt haben, ist
in der Anwendung oft zu unflexibel. Als Beispiel dazu wollen wir eine Aussage über die
Folge der Fibonacci–Zahlen betrachten. Diese sind wie folgt induktiv definiert (vgl. auch
Abschnitt 1.3.1 und ): (vgl. auch Abschnitt 1.3.1):
fib(0) =df fib(1) =df 1; fib(n+1) =df fib(n)+fib(n-1).
Die zu beweisende Aussage lautet:
∀ n ∈ N. fib(n) ≤ 2n .
Offensichtlich läßt sich der Beweis mittels vollständiger Induktion nicht unmittelbar führen,
da im Induktionsschluß nicht nur die Induktionsvoraussetzung für den Vorgänger n − 1
von n, sondern auch für den Vorvorgänger n − 2 von n benötigt wird.
Der folgende Satz, welcher das vollständige Induktionsprinzip verallgemeinert, verschafft
einen Ausweg aus dem Dilemma.
Satz 1.26 (Verallgemeinertes Induktionsprinzip)
Läßt sich eine Aussage über natürliche Zahlen für jede natürliche Zahl aus der Gültigkeit
der Aussage für alle kleineren natürlichen Zahlen ableiten, dann ist sie für jede natürliche
Zahl wahr.
∀n ∈ N. ∀ m < n. A(m) ⇒ A(n) ⇒ ∀ n ∈ N. A(n) .
3
Beweis
Zur Abkürzung bezeichnen wir in dieser Aufgabe einen Induktionsbeweis für Eigenschaft
A, der gemäß dem Prinzip der vollständigen Induktion geführt wird, mit VSI (A) und
einen Induktionsbeweis für Eigenschaft A, der gemäß dem Prinzip der verallgemeinerten
Induktion geführt wird, mit VAI (A). Weiterhin bezeichen wir Prämisse und Konklusion
von Induktionsbeweisen für eine Eigenschaft A durch Voranstellen der Buchstaben P und
K. Es ergibt sich also folgendes Bild für die verwendeten Notationen:
∀n ∈ N. ∀ m < n. A(m) ⇒ A(n) ⇒ ∀ n ∈ N. A(n)
{z
}
|
|
{z
}
PVAI (A)
KVAI (A)
VAI (A)
31
1.3. STRUKTUR UND INVARIANZ
und
A(1) ∧
|
∀ n ∈ N. A(n) ⇒ A(n + 1)
⇒ ∀ n ∈ N. A(n)
|
{z
}
{z
}
VSI (A)
KVSI (A)
PVSI (A)
Sei A : N → B eine Aussage, die mittels verallgemeinerter Induktion bewiesen werden
kann, also durch einen Beweis VAI (A). Dann zeigen wir, daß ein solcher Beweis auch
immer durch einen Beweis mittels vollständiger Induktion geführt werden kann. Dieser
Beweis wird allerdings für eine Aussage A0 geführt, die definiert ist wie folgt:
∀ n ∈ N. A0 (n) =df
∀ m < n. A(n)
VAI (A), also die Implikation PVAI (A) ⇒ KVAI (A), wird nun dadurch gezeigt, dass die
Implikationskette
(1)
PVAI (A) ⇒ PVSI (A0 )
VSI (A0 )
⇒
(2)
KVSI (A0 ) ⇒ KVAI (A).
als gültig nachgewiesen wird. Die zweite Implikation ist dabei unmittelbare Konsequenz
aus der Gültigkeit des Induktionsprinzps der vollständigen Induktion. Es verbleibt damit
die Gültigkeit der mit (1) und (2) gekennzeichneten Implikationen nachzuweisen.
Zu (1): Hier gilt:
PVAI (A) ⇒
⇒
⇒
⇒
⇒
∀ n ∈ N. ∀ m < n. A(m) ⇒ A(n)
∀ n ∈ N. A0 (n) ⇒ A(n)
∀ n ∈ N. A0 (n) ⇒ A0 (n) ∧ A(n)
∀ n ∈ N. A0 (n) ⇒ A0 (n + 1)
A0 (1) ∧ ∀ n ∈ N. A0 (n) ⇒ A0 (n + 1)
⇒ PVSI (A0 )
[Def. PVAI (A)]
[Def. A0 (n)]
[A0 (n) ⇒ A0 (n)]
[Def. A0 (n + 1)]
[A0 (1) ist trivial]
[Def. PVSI (A0 )]
Zu (2): Hier gilt:
KVSI (A0 ) ⇒
∀ n ∈ N. A0 (n)
⇒
∀ n ∈ N. A0 (n + 1)
⇒
∀ n ∈ N. A(n)
⇒ KVAI (A)
[Def. KVSI (A0 )]
[trivial]
[Def. A0 (n + 1)]
[Def. KVAI (A)]
2
Mit Hilfe dieses Satzes können wir nun obige Aussage über die Fibonacci–Zahlen formal
beweisen.
Für die Fälle n = 0 und n = 1 ergibt sich die Aussage wie folgt:
32
KAPITEL 1. EINFÜHRUNG
fib(0) = 1 ≤ 1 = 20 und fib(1) = 1 ≤ 2 = 21 .
Diese beiden Fälle entsprechen dem Induktionsanfang bei der vollständigen Induktion. Für
ihren Beweis steht noch keine Induktionsvoraussetzung zur Verfügung. Als Besonderheit
fällt hier auf, daß der Induktionsanfang für 0 und 1, also für zwei Elemente, gemacht
werden muß. Das wäre nach dem Schema der vollständigen Induktion unmöglich.
Der folgende Teil des Beweises entspricht dem Induktionsschluß bei der vollständigen Induktion. Jedoch können wir nun anders als dort von n > 1 ausgehen, da der Fall n = 1
bereits als zweiter Induktionsanfang abgehandelt wurde. Dies ist wesentlich, um die Induktionsvoraussetzung an den beiden nötigen Stellen anwenden zu können. Damit ergibt
sich die folgende Ungleichheitskette:
fib(n)
= fib(n − 2) + fib(n − 1)
(Induktionsvoraussetzung für k = n − 2) ≤ 2n−2 + fib(n − 1)
(Induktionsvoraussetzung für k = n − 1) ≤ 2n−2 + 2n−1
= 3 ∗ 2n−2
≤ 4 ∗ 2n−2
= 2n .
Mit Satz 1.26 folgt die Behauptung.
Strukturelle Induktion
Auch das verallgemeinerte Induktionsprinzip ist noch etwas schwerfällig, wenn es darum
geht, Aussagen über allgemeine induktiv definierte Strukturen zu beweisen. Sei also S im
folgenden eine beliebige induktiv definierte Struktur mit atomaren Objekten aus A und
Operatoren aus Op. Bezeichnen wir nun die Menge aller maximalen echten Bausteine eines
Elements s von S mit Komp(s), so gilt:
Satz 1.27 (Strukturelle Induktion)
Läßt sich eine Aussage über S für jedes Element von S aus der Gültigkeit der Aussage für
alle kleineren Bausteine ableiten, dann ist sie für jedes Element von S wahr.
∀s ∈ S. ∀s0 ∈ Komp(s). A(s0 ) ⇒ A(s)
⇒ ∀ s ∈ S. A(s) .
3
Beweis
Sei also A eine Aussage über S, die sich durch strukturelle Induktion beweisen läßt.
33
1.3. STRUKTUR UND INVARIANZ
Da S induktiv definiert ist, läßt sich jedes Element s von S in endlich vielen Schritten
aus atomaren Bausteinen zusammensetzen. Insbesondere ist die Funktion l : S → N, die
jedem Element von S die minimale Anzahl der für seine Konstruktion nötigen Konstruktionsschritte zuordnet, wohldefiniert. Damit läßt sich die Aussage A über Elemente von S
in eine Aussage A0 über natürliche Zahlen umwandeln:
∀n ∈ N. A0 (n) =df
(∀s ∈ S. (l(s) = n) ⇒ A(s))
Offenbar genügt es nun zu zeigen, daß sich die Aussage A 0 mit verallgemeinerter Induktion
beweisen läßt (basierend auf unserer Annahme, daß sich A durch strukturelle Induktion
beweisen läßt).
Seien also n ∈ N und s ∈ S mit l(s) = n. Wegen der allgemeinen Wahl von s genügt es
nun, A(s) zu zeigen, um die Gültigkeit von A0 (n) nachweisen.
Im Falle von n = 1 ist s offenbar ein atomares Objekt. Der Erfolg der strukturellen
Induktion liefert damit unmittelbar die Gültigkeit von A(s).
Sei also nun n > 1. Dann hat s die Gestalt f (s 1 , · · · , sk ) für geeignete si ∈ S und es gilt
nach Konstruktion:
∀ 1 ≤ i ≤ k. l(si ) < n
und damit nach Induktionsvoraussetzung (der verallgemeinerten Induktion):
∀1 ≤ i ≤ k. A(si )
Da aber {s1 , · · · , sk } = Komp(s) gilt, ist das gerade die Induktionsvoraussetzung, die im
strukturellen Induktionsbeweis für A auftritt. Die Gültigkeit des strukturellen Induktionsbeweises liefert also unmittelbar die Gültigkeit von A(n).
2
Strukturelle Definition von Funktionen und Eigenschaften
Grundlage für die strukturelle Definition von Funktionen und Eigenschaften ist die induktive Definition des zugrundeliegenden Definitionsbereichs, dessen Struktur sie folgt:
Die strukturelle Definition einer Funktion f mit induktiv definiertem Definitionsbereich
erfolgt (im elementaren Fall) durch
1. Festlegung von f für die atomaren Bausteine des Definitionsbereichs und
2. Angabe einer Vorschrift, wie sich die Funktionswerte komplexerer Bausteine relativ
zu den Funktionswerten ihrer Komponenten berechnen.
34
KAPITEL 1. EINFÜHRUNG
Folgen wir dem Format der ‘verallgemeinerten’ Induktion, so erhalten wir eine Beschreibungsform, die sich auf eine endliche Anzahl bekannter Funktionen auf dem Wertebereich
stützt. Die Funktionsdefinition besteht dann aus einer Fallunterscheidung bzgl. der syntaktischen Struktur des betrachteten Elements s ∈ S. Für jeden Fall gibt es dann eine
definierende Gleichung der Form:
f (s) =df gcons (f (s1 ), .., f (sk )),
falls s ein durch Konstruktor cons zusammengesetztes Objekt der Form s = cons(s 1 , .., sk ), (k ≥
0) ist und gcons eine auf dem Wertebereich bekannte Funktion ist.
1.3.3
Anwendung: Semantik Boolescher Terme
Nun wollen wir einem beliebigen Booleschen Term seine Bedeutung zuordnen. Dazu benötigen wir zunächst noch einige Begriffe.
Definition 1.28 (Wahrheitswert, Belegung, Umgebung)
Die Menge der Booleschen Wahrheitswerte {tt, ff } ( wahr“ und falsch“) wird mit
”
”
B bezeichnet.
Eine Funktion β : ID −→ B, die jedem Identifikator einen Wahrheitswert zuordnet, heißt
Belegung oder Umgebung. Die Menge aller Belegungen wird mit ENV (für environ”
ment“) bezeichnet.
3
Definition 1.29
Die Booleschen Funktionen neg : B −→ B und conj, disj : B × B −→ B sind wie folgt durch
sog. Wahrheitstafeln definiert:
neg tt ff
ff tt
conj tt ff
tt
tt ff
ff ff ff
disj tt ff
tt tt tt
ff tt ff
3
Durch folgende Definition wird nun Booleschen Termen eine Bedeutung (ein Wahrheitswert) zugeordnet:
Definition 1.30 (Semantikfunktion)
Die Semantikfunktion für Boolesche Terme ist eine Funktion [[ · ]] : BT −→ (ENV → B),
die einem Booleschen Term unter Zuhilfenahme einer Belegung einen Wahrheitswert zuordnet. Sie ist wie folgt induktiv definiert:
35
1.3. STRUKTUR UND INVARIANZ
• [[ true ]](β) =df tt
• [[ false ]](β) =df ff
• [[ id ]](β) =df β(id) für alle id ∈ ID
• [[ (¬t1 ) ]](β) =df neg([[ t1 ]](β))
• [[ (t1 ∧ t2 ) ]](β) =df conj([[ t1 ]](β), [[ t2 ]](β))
• [[ (t1 ∨ t2 ) ]](β) =df disj([[ t1 ]](β), [[ t2 ]](β))
Das Semantikschema für Boolesche Terme ist somit hBT, ENV → B, [[ · ]]i, wobei
ENV → B die Menge der Funktionen von ENV nach B ist.
3
Beispiel 1.31
Sei β(x) = β(y) = tt und β(z) = ff. Dann gilt:
[[ (¬((x ∧ y) ∨ z)) ]](β)
(Definition von [[ (¬t) ]])
= neg([[ ((x ∧ y) ∨ z) ]](β))
(Definition von [[ (t1 ∨ t2 ) ]])
= neg(disj([[ (x ∧ y) ]](β), [[ z ]](β)))
(Definition von [[ (t1 ∧ t2 ) ]])
= neg(disj(conj([[ x ]](β), [[ y ]](β)), [[ z ]](β)))
(Definition von [[ id ]], id ∈ ID) = neg(disj(conj(β(x), β(y)), β(z)))
(Einsetzen von β )
= neg(disj(conj(tt, tt), ff ))
(Definition von conj)
= neg(disj(tt, ff ))
(Definition von disj)
= neg(tt)
(Definition von neg)
= ff.
3
Bevor wir die semantische Äquivalenz Boolescher Terme definieren, führen wir zunächst
einige rein syntaktische Abkürzungen und Konventionen ein.
Boolescher Terme in vereinfachter Syntax
Konvention 1.32
Zur Vereinfachung der Schreibweise Boolescher Terme vereinbaren wir folgende Abk ürzungen (wobei n ∈ N):
36
KAPITEL 1. EINFÜHRUNG
• (t1 ⇒t2 ) (lese: t1 impliziert t2“) als Abkürzung für ((¬t1 ) ∨ t2 ) (Implikation),
”
• (t1 ⇔t2 ) (lese: t1 äquivalent t2“) für ((t1 ∧ t2 ) ∨ ((¬t1 ) ∧ (¬t2 ))) (Äquivalenz),
”
n
W
ti für (...((t1 ∨ t2 ) ∨ t3 ) ∨ ... ∨ tn ) (endliche Disjunktion) und
•
i=1
•
n
V
ti für (...((t1 ∧ t2 ) ∧ t3 ) ∧ ... ∧ tn ) (endliche Konjunktion).
i=1
3
Konvention 1.33
Weiterhin definieren wir zur Ersparnis von Klammern eine Bindungspr äzedenz:
• Der einstellige Operator ¬ bindet stärker als alle anderen Operatoren.
• ∧ bindet stärker als ∨.
• ∨ bindet stärker als ⇒ und ⇔.
Wir verstehen aber weiterhin Terme, bei denen mithilfe dieser Regeln Klammern eingespart wurden, lediglich als Abkürzung für die entsprechenden vollständig geklammerten
Terme.
3
Beispiel 1.34
In den folgenden Beispielen bezeichnet =“ die syntaktische Gleichheit auf Booleschen
”
Termen.
• (x ∧ y ∨ z) = ((x ∧ y) ∨ z) 6= (x ∧ (y ∨ z))
• (x ∧ y) 6= (y ∧ x)
• (x ∨(y ∨ z)) 6= ((x ∨ y) ∨ z)
• ((x ∨ y) ⇒ z) = ((¬(x ∨ y)) ∨ z)
• (¬x ∨ y) = ((¬x) ∨ y) 6= (¬(x ∨ y))
• (x ∨ y ⇒ z) = ((x ∨ y) ⇒ z) 6= (x ∨ (y ⇒ z))
3
Die syntaktische Gleichheit betrachtet also nicht die Bedeutung eines Termes. Insbesondere heißt das, daß beispielsweise das Kommutativ– oder das Assoziativgesetz auf der
Syntax Boolescher Terme nicht gilt. Ein semantischer Gleichheitsbegriff ist Gegenstand
des folgenden Abschnittes.
37
1.3. STRUKTUR UND INVARIANZ
Semantische Äquivalenz Boolescher Terme
Die Semantikfunktion [[ · ]], die Booleschen Termen ihre Bedeutung zuordnet, ist nicht
injektiv. Das heißt, daß etwa die beiden syntaktisch verschiedenen Booleschen Terme
t1 ∧ t2 und t2 ∧ t1 die gleiche Semantik besitzen. Dies liegt letztendlich an der Kommutativität von conj. Solche semantischen Gleichheiten oder Äquivalenzen wollen wir nun formal
erfassen:
Definition 1.35 (Semantische Äquivalenz)
Zwei Boolesche Terme t1 und t2 heißen (semantisch) äquivalent, in Zeichen t1 ≡ t2 ,
falls gilt:
∀ β ∈ ENV. [[ t1 ]](β) = [[ t2 ]](β).
3
Beispiel 1.36
Es gelten für beliebige Boolesche Terme t, t1 , t2 , t3 die folgenden Äquivalenzen:
(t ∧ t)
(t ∨ t)
≡
≡
t
t
(t1 ∧ t2 )
(t1 ∨ t2 )
≡
≡
(t2 ∧ t1 )
(t2 ∨ t1 )
((t1 ∧ t2 ) ∧ t3 )
((t1 ∨ t2 ) ∨ t3 )
≡
≡
(t1 ∧ (t2 ∧ t3 ))
(t1 ∨ (t2 ∨ t3 ))
(t1 ∧ (t1 ∨ t2 ))
(t1 ∨ (t1 ∧ t2 ))
≡
≡
t1
t1
(t1 ∧ (t2 ∨ t3 ))
(t1 ∨ (t2 ∧ t3 ))
¬¬t
≡
≡
≡
((t1 ∧ t2 ) ∨ (t1 ∧ t3 ))
((t1 ∨ t2 ) ∧ (t1 ∨ t3 ))
t
¬(t1 ∧ t2 )
¬(t1 ∨ t2 )
≡
≡
(¬t1 ∨ ¬t2 )
(¬t1 ∧ ¬t2 )
(true ∧ t)
(f alse ∨ t)
≡
≡
t
t
(t ∧ ¬t)
(t ∨ ¬t)
≡
≡
f alse
true
(Idempotenz)
(Kommutativität)
(Assoziativität)
(Absorption)
(Distributivität)
(Doppelnegation)
(deMorgansche Regeln)
(Neutralität)
(Negation)
38
KAPITEL 1. EINFÜHRUNG
Diese Aussagen können leicht mit Hilfe von Wahrheitstafeln bewiesen werden.
3
Definition 1.37 (Äquivalenzrelation)
Eine binäre Relation R über einer Menge M heißt Äquivalenzrelation, falls für beliebige
x, y, z ∈ M gilt:
• (x, x) ∈ R (Reflexivität),
• (x, y) ∈ R impliziert (y, x) ∈ R (Symmetrie) und
• (x, y) ∈ R und (y, z) ∈ R impliziert (x, z) ∈ R (Transitivit ät).
3
Äquivalenzrelationen über einer Menge M stehen in einer engen Beziehung zu sogenannten
Partitionen von M .
Näheres dazu: Zusatzfolien zu Äquivalenzrelationen auf der DAP1-Seite.
Lemma 1.38
Die (semantische) Äquivalenz ≡ von Booleschen Termen ist eine Äquivalenzrelation.
3
Der Beweis von Lemma 1.38 ist eine Übungsaufgabe. Er ist unmittelbar anhand der Definitionen 1.35 und 1.37 zu führen.
Substitutionen
Gleichheitsbeweise durch algebraisches Umformen basieren auf einer Reihe von bewiesenen
elementaren Gleichheiten zwischen Termen. Beweisidee ist, den einen Term solange durch
‘Ersetzen von Gleichem durch Gleiches’ umzuformen, bis der zweite Term erreicht wird. Die
elementaren Gleichheiten nennt man auch Axiome und die beschriebene Vorgehensweise
axiomatisch.
Leider lassen sich nicht alle Gleichheitsbegriffe (z.B. nicht jede semantische Äquivalenz)
axiomatisieren. Grundvoraussetzung für die Axiomatisierbarkeit eines Gleichheitsbegriffs
ist, daß er eine Kongruenz definiert, d.h. daß das Prinzip ‘Ersetzen von Gleichem durch
Gleiches’ gültig ist. Damit haben wir ein weiteres zentrales Designkriterium für die Definition eines Semantikschemas. Wie wir sehen werden, ist im Falle der semantischen Äquivalenz
für Boolesche Terme die Welt in Ordnung, vgl. Kompositionalitätssatz 1.41.
1.3. STRUKTUR UND INVARIANZ
39
Ein weiteres wichtiges Designkriterium für Semantikschemata ist das korrekte Zusammenspiel semantischer und syntaktischer Manipulationen, oder technisch ausgedr ückt, von
semantischer und syntaktischer Substitution. Auch dieses Kriterium ist für unser Semantikschema für Boolesche Terme erfüllt, vgl. Substituionslemma 1.43.
Definition 1.39 (Syntaktische Substitution)
Die syntaktische Substitution ist eine dreistellige Abbildung
·[·/·] : BT × BT × ID −→ BT.
Intuitiv ist t1 [t2 /x] der Term, der entsteht, wenn wir in t 1 den Identifikator x an allen
Stellen durch den Term t2 ersetzen (lese: t1 mit t2 für x“).
”
Formal ist die Substitution für Boolesche Terme t, t1 , t2 ∈ BT und Identifikatoren x, y ∈ ID
induktiv wie folgt definiert:
• true[t/x] =df true
• false[t/x] =df false
(
t falls y = x
• y[t/x] =df
y sonst
• (¬t1 )[t/x] =df (¬t1 [t/x])
• (t1 ∧ t2 )[t/x] =df (t1 [t/x] ∧ t2 [t/x])
• (t1 ∨ t2 )[t/x] =df (t1 [t/x] ∨ t2 [t/x])
In t1 [t2 /x] heißt t1 Kontext (von x), und jedes Auftreten von x heißt Redex (oder
Anwendungsstelle).
Weiterhin sei die Präzedenz von ·[·/·] größer als die aller anderen Symbole.
3
Beispiel 1.40
(x ∨ (y ∧ x))[t/x] = (t ∨ (y ∧ t))
(¬(y ∧ x))[t/x] = (¬(y ∧ t))
3
Satz 1.41 (Kompositionalität)
Seien t, t0 , t00 ∈ BT mit t0 ≡ t00 . Dann gilt
t[t0 /x] ≡ t[t00 /x],
d.h. man darf (simultan) Gleiches durch (semantisch) Gleiches ersetzen.
3
40
KAPITEL 1. EINFÜHRUNG
Beweis
Seien t, t0 , t00 ∈ BT beliebig mit t0 ≡ t00 . Nach Definition 1.35 der semantischen Äquivalenz
ist für eine beliebige Belegung β ∈ ENV zu zeigen:
(∗)
[[ t[t0 /x] ]](β) = [[ t[t00 /x] ]](β).
Wir zeigen (∗) mittels struktureller Induktion über den Termaufbau von t:
1. Fall: t ∈ {true, false, y} für y ∈ ID mit y 6= x
Dann folgt (∗) wegen t[t0 /x] = t = t[t00 /x].
2. Fall: t = x
Hier folgt (∗) mit der Voraussetzung t 0 ≡ t00 :
x[t0 /x] = t0 ≡ t00 = x[t00 /x].
3. Fall: t = (¬t1 )
[[ (¬t1 )[t0 /x] ]](β)
(Def. von [·/·])
= [[ (¬(t1 [t0 /x])) ]](β)
(Definition von [[ · ]])
= neg([[ t1 [t0 /x] ]](β))
(Induktionsannahme) = neg([[ t1 [t00 /x] ]](β))
(Definition von [[ · ]])
= [[ (¬(t1 [t00 /x])) ]](β)
(Def. von [·/·])
= [[ (¬t1 )[t00 /x] ]](β)
4. Fall: t = (t1 ∧ t2 )
[[ (t1 ∧ t2 )[t0 /x] ]](β)
(Def. von [·/·])
= [[ (t1 [t0 /x] ∧ t2 [t0 /x]) ]](β)
(Definition von [[ · ]])
= conj([[ t1 [t0 /x] ]](β), [[ t2 [t0 /x] ]](β))
(Induktionsannahme)
= conj([[ t1 [t00 /x] ]](β), [[ t2 [t00 /x] ]](β))
(Definition von [[ · ]])
(Def. von [·/·])
= [[ (t1 [t00 /x] ∧ t2 [t00 /x]) ]](β)
= [[ (t1 ∧ t2 )[t00 /x] ]](β)
5. Fall: t = (t1 ∨ t2 )
Dieser Fall ist analog zu t = (t1 ∧ t2 ) (Ersetzen von ∧“ durch ∨“ und von conj“
”
”
”
durch disj“).
”
1.3. STRUKTUR UND INVARIANZ
41
Per Induktionsprinzip folgt die Behauptung.
2
Substitutionen gibt es auch auf der Semantikseite“ für Belegungen.
”
Definition 1.42 (Semantische Substitution)
Die semantische Substitution ist eine dreistellige Abbildung
·{·/·} : ENV × B × ID −→ ENV
definiert durch:
β{b/x}(y) =df
(
b
β(y)
falls y = x
sonst
3
Semantische Substitutionen ändern also Belegungen an einer Stelle und lassen den Wert
an alle anderen Stellen unverändert. Das folgende Lemma, welches die Verträglichkeit der
syntaktischen Substitution mit der semantischen Substitution charakterisiert, spielt in der
Logik eine zentrale Rolle:
Lemma 1.43 (Substitutionslemma)
Für beliebige Boolesche Terme t, t0 ∈ BT, eine beliebige Umgebung β ∈ ENV und einen
beliebigen Identifikator x0 ∈ ID gilt:
[[ t[t0 /x0 ] ]](β) = [[ t ]](β{[[ t0 ]](β)/x0 }),
wobei [t0 /x0 ] die syntaktische Substitution und {[[ t 0 ]](β)/x0 } die semantische Substitution
repräsentiert.
3
Beweis
Wir beweisen die Aussage mittels struktureller Induktion über den Termaufbau von t:
1. Fall: t = true
[[ true[t0 /x0 ] ]](β) = [[ true ]](β) = tt = [[ true ]](β{[[ t 0 ]](β)/x0 }).
2. Fall: t = false
Dieser Fall geht analog zum 1. Fall.
3. Fall: t = x für x ∈ ID
(a) x = x0 :
[[ x[t0 /x] ]](β)
(Definition von [·/·])
= [[ t0 ]](β)
= (β{[[ t0 ]](β)/x})(x)
(Definition von [[ · ]]) = [[ x ]](β{[[ t0 ]](β)/x})
42
KAPITEL 1. EINFÜHRUNG
(b) x 6= x0 :
[[ x[t0 /x0 ] ]](β)
= [[ x ]](β)
(Definition von [[ · ]]) = β(x)
= (β{[[ t0 ]](β)/x0 })(x)
(Definition von [[ · ]]) = [[ x ]](β{[[ t0 ]](β)/x0 })
4. Fall: t = (¬t1 )
[[ (¬t1 )[t0 /x0 ] ]](β)
(Definition von [·/·])
= [[ ¬(t1 [t0 /x0 ]) ]](β)
(Definition von [[ · ]])
= neg([[ t1 [t0 /x0 ] ]](β))
(Induktionsannahme) = neg([[ t1 ]](β{[[ t0 ]](β)/x0 }))
(Definition von [[ · ]])
= [[ (¬t1 ) ]](β{[[ t0 ]](β)/x0 })
5. Fall: t = (t1 ∧ t2 )
[[ (t1 ∧ t2 )[t0 /x0 ] ]](β)
(Definition von [·/·])
= [[ (t1 [t0 /x0 ] ∧ t2 [t0 /x0 ]) ]](β)
(Definition von [[ · ]])
= conj([[ t1 [t0 /x0 ] ]](β), [[ t2 [t0 /x0 ] ]](β))
(Induktionsannahme)
= conj([[ t1 ]](β{[[ t0 ]](β)/x0 }), [[ t2 ]](β{[[ t0 ]](β)/x0 }))
(Definition von [[ · ]])
= [[ (t1 ∧ t2 ) ]](β{[[ t0 ]](β)/x0 })
6. Fall: t = (t1 ∨ t2 )
Dieser Fall geht analog zu t = (t1 ∧ t2 ) (Ersetzen von ∧“ durch ∨“ und von conj“
”
”
”
durch disj“).
”
Per Induktionsprinzip folgt die Behauptung.
Eine unmittelbare Konsequenz aus dem Substitutionslemma ist folgendes Resultat:
2
1.3. STRUKTUR UND INVARIANZ
43
Korollar 1.44 (Verträglichkeit)
Für beliebige Boolesche Terme t, t0 ∈ BT, eine beliebige Umgebung β ∈ ENV und einen
beliebigen Identifikator x ∈ ID gilt:
[[ x ]](β) = [[ t0 ]](β) impliziert [[ t ]](β) = [[ t[t0 /x] ]](β).
3
Beweis
Seien t, t0 ∈ BT, x ∈ ID und β ∈ ENV, derart daß [[ x ]](β) = [[ t 0 ]](β). Dann gilt:
[[ t[t0 /x] ]](β)
Lem. 1.43
=
[[ t ]](β{[[ t0 ]](β)/x}) = [[ t ]](β{[[ x ]](β)/x}) = [[ t ]](β{β(x)/x}) = [[ t ]](β)
2
Kapitel 2
WHILE–Programme und ihre
Semantik
2.1
Einleitung
Eine Programmiersprache ist durch ihre Syntax und ihre Semantik festgelegt. Die Syntax beschreibt, wie ein formal korrekt aufgebautes Progamm auszusehen hat. Solche Programme können mit Hilfe eines Compilers in Code übersetzt werden, welcher auf einer
Maschine ausführbar ist. Die Semantik legt fest, was die Ausführung eines formal korrekten
Programms auf dem Rechner bewirkt. Bei imperativen Sprachen wie z.B. WHILE wird
dieser Effekt in der Regel mit der durch das Programm hervorgerufenen Transformation
des Speicherzustands identifiziert.
Die formale Beschreibung der Syntax einer Programmiersprache z.B. mit Hilfe von BNFen
ist schon seit langem selbstverständlich. Leider sieht dies in der Praxis bei Semantikbeschreibungen oft noch ganz anders aus. Informelle Semantikbeschreibungen wirken auf den
ersten Blick einfacher und verständlicher. Diesem Vorteil steht aber ein Mangel an Präzision gegenüber, dessen Konsequenzen sich nicht von vorneherein abschätzen lassen. Das
führt zu Unsicherheiten sowohl während des Sprachdesigns als auch während der späteren
Programmierung. Im Gegensatz dazu stehen die zunächst kompliziert wirkenden formalen
Beschreibungsmethoden, deren Präzision die strukturelle Entwicklung von
• Programmiersprachen (fehlende Details, Mehrdeutigkeiten und Inkonsistenzen können frühzeitig erkannt werden),
• Compilern (bei geeigneter Beschreibungsmethode können Compiler sogar automatisch erzeugt werden, z.B. durch attributierte Grammatiken, siehe Informatik II),
und
44
2.1. EINLEITUNG
45
• Programmen (ein genaueres Verständnis der Semantik verhindert Unsicherheiten bei
der Implementierung; außerdem können gemäß einem Standard, d.h. gemäß einer
allgemein anerkannten Syntax– und Semantikbeschreibung entwickelte Programme
problemlos portiert werden)
ermöglicht. Darüber hinaus unterstützen sie Korrektheitsbeweise für
• Compiler: Sowohl die Standardübersetzung“ als auch Randbedingungen für opti”
mierende Programmtransformationen können verifiziert werden.
• Programme: Die Bedeutung eines Programms kann anhand der Semantikbeschreibung formal hergeleitet und dann mit dem gewünschten Programmverhalten verglichen werden.
Klassische formale Methoden zur Semantikbeschreibung von Programmiersprachen sind
• operationelle Semantiken: Die Bedeutung eines Programms wird durch das Verhalten (die Berechnung) spezifiziert, das es auf der Maschine hervorruft (Laufzeitverhalten). Hier steht also im Vordergrund, wie ein Resultat erzielt wird. Dieses
über den Effekt als Zustandstransformation hinausgehende Implementierungswissen
ist von zentraler Bedeutung, wenn eine Programmiersprache mittels eines Compilers
auf einem Rechner realisiert werden soll. Für die Analyse einzelner in der betrachteten Programmiersprache geschriebenen Programme ist die operationelle Semantik
in der Regel zu schwerfällig.
• denotationelle Semantiken: Die Bedeutung eines Programmkonstruktes wird mit
Hilfe mathematischer Objekte (üblicherweise Funktionen) modelliert. Dabei steht
allein der Effekt eines Programms als Zustandstransformation im Vordergrund.
Von den aktuellen Implementierungsdetails wird bewußt abstrahiert. Die ‘praktische’ Relevanz der denotationellen Semantiken ist durch ihre Kompositionalit ät
begründet: Die Semantik eines komplexen Programmstücks wird direkt aus den Semantiken seiner (unmittelbaren) Komponenten zusammengesetzt. Wie diese Komponenten ihre Semantik realisieren, ist dabei irrelevant. Diese laufzeitunabh ängigen
Semantikspezifikationen eignen sich zur Unterstützung des Programmentwurfs nach
dem Prinzip der schrittweisen Verfeinerung und zur (induktiven) Programmverifikation.
• axiomatische Semantiken: Spezielle Eigenschaften des durch die Programmausführung hervorgerufenen Effekts werden durch Vor– und Nachbedingungen ausgedrückt, deren Korrektheit sich mittels des Hoare–Kalküls halbautomatisch nachweisen läßt. Das erlaubt das Vernachlässigen von im Moment als uninteressant erachteten Details. Insbesondere abstrahiert die axiomatische Semantik wie die denotationelle Semantik von Implementierungsdetails, und ähnlich wie diese eignet sie sich
46
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
auch zur Unterstützung des Programmentwurfs nach dem Prinzip der schrittweisen
Verfeinerung und zur (halbautomatischen) Programmverifikation.
2.2
Syntax von WHILE
In diesem Abschnitt wird die Syntax der Sprache WHILE definiert.
Wir spezifizieren die (konkrete) Syntax von WHILE–Programmen mit Hilfe einer BNF.
Diese Notation haben wir schon im vorangegangenen Kapitel kennengelernt. Die so spezifizierten (syntaktisch korrekten) Programmfragmente sind in fünf syntaktische Bereiche
gegliedert. Diese fünf Bereiche und ihre zugehörigen Metavariablen sind:
• die Zahlen Num mit Metavariable n,
• die Variablen Var mit Metavariable x,
• die arithmetischen Ausdrücke Aexp mit Metavariable a,
• die Booleschen Ausdrücke Bexp mit Metavariable b. Wir unterscheiden explizit
zwischen den auf Seite 21 eingeführten Booleschen Termen BT, die Formeln auf
logischem Niveau (Meta-Level) beschreiben und Booleschen Ausdrücken Bexp, die
Bedingungen innerhalb der Sprasche der WHILE-Programme modellieren.
• die Anweisungen Stm mit Metavariable S.
Wir verzichten hier auf detailliertere Darstellungen von Zahlen und Variablen, und betrachten diese als gegebene Objekte. Die Struktur der anderen Sprachkonstrukte ist nun
induktiv durch folgende BNF gegeben:
a ::= n | x | (a + a) | (a ∗ a)
b ::= true | false | (a == a) | (a < a) | (a <= a) | (!b) | (b && b) | (b || b)
S ::= x = a | skip | S; S | if (b) {S} else {S} | while (b) {S}
Beispiel 2.1
Wie die syntaktische Korrektheit eines While–Programms mit Hilfe der BNF-Definition
nachgewiesen werden kann, wird am Beispielprogramm
y = 1; while (1 < x) {y = y ∗ x; x = x − 1 }
aus Abbildung 2.1 vorgeführt. Bei der Interpretation von 1 ∈ Num durch 1 ∈ Z berechnet
das Beispielprogramm die Fakultät des Wertes von x.
Ein While–Programm ist syntaktisch korrekt, wenn es sich
2.2. SYNTAX VON WHILE
47
S
S;S
y=a
n
while (b) {S}
(a<a)
n x
S;S
y=a
x=a
(a*a)
(a−a)
xy
x n
Abbildung 2.1: Ableitung eines While–Programms aus der BNF
• auf das Axiom S reduzieren läßt, d.h. wenn es sich durch sukzessive ‘Termersetzung’ entlang der von rechts nach links gelesenen BNF–Regeln in das Symbol S
überführen läßt, oder äquivalent dazu, wenn es sich
• aus dem Axiom ableiten läßt, d.h. wenn es durch sukzessive ‘Termersetzung’ entlang
der von links nach rechts gelesenen BNF-Regeln aus dem Symbol S erzeugen läßt.
3
Abschließend wollen wir noch auf eine grafische Beschreibungsmethode für die Syntax von
Programmiersprachen eingehen, die sog. Syntaxdiagramme. Auch diese zu den BNFSpezifiaktionen äquivalente Methode demonstrieren wir anhand von WHILE.
Grundbausteine für Syntaxdiagramme sind durch Pfeile verbundene, beschriftete rechteckige und ovale Kästchen. Diese sind so anzuordnen, daß ein gerichteter Graph mit genau einem Eingangs- und einem Ausgangspfeil entsteht. Diese Struktur entspricht der
rechten Seite einer BNF–Gleichung. Das linksseitig auftretende Nichtterminalsymbol der
BNF–Gleichungen spiegelt sich darin wider, daß Syntaxdiagramme benannt sind. Auf diese
Weise kann in den verschiedenen Syntaxdiagrammen, die eine Syntaxdefinition ausmachen,
wechselseitig aufeinander Bezug genommen werden: Innerhalb der rechteckigen Kästchen
der Syntaxdiagramme stehen Namen, die das Syntaxdiagramm bezeichnen, das während
eines Ableitungsschritts anstelle des Kästchens einzukopieren ist. Bezeichner in den ovalen
Kästchen sind Programmtexte. Sie sind wie die Terminalsymbole der BNF einfach an den
betreffenden Stellen einzusetzen.
48
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
Um nun ein syntaktisch korrektes Programm zu erhalten, durchläuft man das Syntaxdiagramm den Pfeilen folgend vom Eingangspfeil zum Ausgangspfeil, wobei man rechteckige Kästchen durch ihre definierenden Syntaxdiagramme ersetzt und alle Texte in ovalen
Kästchen sukzessive notiert. Die Bedeutung eines Syntaxdiagramms im Rahmen einer umfassenden Syntaxdefintion ist also die Menge aller Terminalsymbolfolgen, denen ein Pfad
obiger Bauart entspricht.
Wie BNF–Definitionen können auch Syntaxdiagramme rekursiv sein, d.h. in dem mit X
bezeichneten Diagramm kann X selbst direkt oder indirekt (d.h. erst nach dem Einkopieren
anderer Syntaxdiagramme) vorkommen. Das ändert aber nichts an den oben genannten
Prinzipien.
Die Syntax von WHILE läßt sich mit Hilfe rekursiver Syntaxdiagramme spezifizieren
(vgl. Abbildung 2.2), wobei wir diesmal auf die Spezifikation der Syntax arithmetischer
und Boolescher Ausdrücke verzichtet haben (vgl. Übung).
Iteration versus Rekursion: In manchen Fällen kann Rekursion durch eine äquivalente iterative Lösung (d.h. durch ein Syntaxdiagramm mit einer Schleife) ersetzt werden.
Beispielsweise bezeichnen die Diagramme aus den Abbildungen 2.3 und 2.4 dieselben Ziffernfolgen.
Immer geht dies jedoch nicht, wie man sich an dem Beispiel beliebig geschachtelter, geklammerter arithmetischer Ausdrücke klarmachen kann. Die Problematik wird an folgendem auf die Essenz vereinfachten (rekursiven) Syntaxdiagramm A aus Abbildung 2.5
klarer, das kein iteratives Äquivalent besitzt. Diese Überlegungen werden in der Vorlesung
“Grundzüge Theoretischer Informatik’ ausführlich diskutiert.
2.3
2.3.1
Semantik von WHILE
Semantik von arithmetischen und Booleschen Ausdrücken
Die Semantik eines Ausdrucks soll seinen Wert im üblichen Sinne widerspiegeln. Dabei
ist zu beachten, daß der Wert eines Ausdrucks von den Wertebindungen der in ihm
vorkommenden Variablen (Identifikatoren) abhängt.
Bemerkung 2.2
Variablenidentifikatoren haben einen Doppelcharakter: als Variable“ sind sie an einen
”
Wert gebunden und als Ausdruck“ haben sie einen Wert. Dieser Doppelcharakter ist
”
zentral für die Interpretation von Zuweisungen: rechtsseitige Vorkommen bezeichnen einen
Ausdruck und linksseitige eine Variable.
3
2.3. SEMANTIK VON WHILE
49
while−Programme (WP)
(Wert) Zuweisung
skip−Anweisung
(sequentielle) Komposition
if−then−else−Anweisung
while−Anweisung
(Wert) Zuweisung
=
Variable
(arithm.) Ausdruck
skip−Anweisung
skip
(sequentielle) Komposition
WP
;
WP
if−then−else−Anweisung
if
{
(
WP
Boolescher Term
}
else
)
{
WP
)
{
}
while−Anweisung
while
(
Boolescher Term
Abbildung 2.2: Syntaxdiagramme für die WHILE–Sprache
Ziffernfolge
Ziffer
Abbildung 2.3: Syntaxdiagramm ohne Rekursion
WP
}
50
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
Ziffernfolge 1
Ziffernfolge 1
Ziffer
Abbildung 2.4: Syntaxdiagramm mit Rekursion
A
a
A
b
Abbildung 2.5: Beispiel
Ein Speicherzustand, im folgenden kurz Zustand genannt, ist ein Element der Menge
Σ =df {σ | σ : Var −→ Z},
wobei Var die Menge aller Variablen bezeichnet. Eine ähnliche Konstruktion hatten wir
schon bei der Definition der Bedeutung Boolescher Terme als ‘Umgebung’ kennengelernt.
Hier gehen wir nun davon aus, daß den Variablen ganze Zahlen, d.h. Elemente von Z,
zugeordnet werden. Weiterhin nehmen wir an, daß alle Variablen in jedem Zustand einen
definierten Wert besitzen.
Die Bedeutung arithmetischer und Boolescher Ausdrücke wird jeweils durch eine Interpretationsfunktion (vgl. Boolesche Terme) definiert:
[[ · ]]A : Aexp −→ (Σ → Z)
und
[[ · ]]B : Bexp −→ (Σ → B),
wobei B wieder die Menge {tt, ff } bezeichne.
Um [[ · ]]A vollständig induktiv definieren zu können, benötigen wir noch eine weitere
Interpretationsfunktion [[ · ]]N : Num −→ Z für Zahlen, die den syntaktischen Objekten
in Num semantische Objekte, hier ganze Zahlen, zuordnet. Weiter gehen wir im folgenden
davon aus, daß Zahlen im Dezimalsystem dargestellt werden. Damit können wir [[ · ]]A
induktiv definieren:
• [[ n ]]A (σ) = [[ n ]]N ( σ bezeichne einen Zustand)
• [[ x ]]A (σ) = σ(x)
• [[ a1 + a2 ]]A (σ) = plus([[ a1 ]]A (σ), [[ a2 ]]A (σ))
• [[ a1 ∗ a2 ]]A (σ) = mul([[ a1 ]]A (σ), [[ a2 ]]A (σ))
2.3. SEMANTIK VON WHILE
51
• [[ a1 − a2 ]]A (σ) = minus([[ a1 ]]A (σ), [[ a2 ]]A (σ))
Hierbei stehen plus“, mul“ und minus“ für die binäre semantische Addition, Multipli”
”
”
kation und Subtraktion, d.h. für die binäre Addition, Multiplikation bzw. Subtraktion in
Z. Der besseren Lesbarkeit halber werden wir später wieder +“, ∗“ bzw. −“ schrei”
”
”
ben, d.h. syntaktische und semantische Operatoren gleich bezeichnen.
Beachte, daß die Semantik von Ausdrücken induktiv entlang des syntaktischen Aufbaus
definiert ist. Das ist die Voraussetzung für spätere Beweise durch strukturelle Induktion.
Beispiel 2.3
Angenommen σ(x) = 3. Dann gilt:
[[ x + 1 ]]A (σ) = [[ x ]]A (σ) + [[ 1 ]]A (σ) = σ(x) + [[ 1 ]]N = 3 + 1 = 4
3
Analog zu [[ · ]]A definieren wir [[ · ]]B . Dazu benötigen wir wie bei der Definition der
Interpretationsfunktion für Boolesche Terme die Hilfsfunktionen neg, conj und disj.
• [[ true ]]B (σ) =df tt
• [[ false ]]B (σ) =df ff
(
• [[ a1 == a2 ]]B (σ) =df
• [[ a1 < a2 ]]B (σ) =df
• [[ a1 <= a2 ]]B (σ) =df
(
tt
ff
tt
ff
(
falls eq([[ a1 ]]A (σ), [[ a2 ]]A (σ))
sonst
falls less([[ a1 ]]A (σ), [[ a2 ]]A (σ))
sonst
tt
ff
falls leq([[ a1 ]]A (σ), [[ a2 ]]A (σ))
sonst
• [[ !b ]]B (σ) =df neg([[ b ]]B (σ))
• [[ b1 && b2 ]]B (σ) =df conj([[ b1 ]]B (σ), [[ b2 ]]B (σ))
• [[ b1 || b2 ]]B (σ) =df disj([[ b1 ]]B (σ), [[ b2 ]]B (σ))
In dieser Definition steht eq“ für die semantische Gleichheit in Z, less“ für die seman”
”
tische kleiner“–Relation in Z und leq“ für die semantische kleiner gleich“–Relation
”
”
”
in Z. Im folgenden werden wir wie üblich =“, <“, ≤“ etc. schreiben. Beachte, daß
”
”
”
wir das Symbol “<” sowohl für die semantische Relation als auch für das syntaktische
Relationszeichen verwenden.
52
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
Die Menge der freien Variablen ist in der Regel für die Semantik von Ausdrücken von
entscheidender Bedeutung. Bei WHILE–Programmen haben wir allerdings eine besondere
Situation: alle Variablenvorkommen sind frei. Das ist aber z.B. im Hoare–Kalk ül anders
(vgl. Abschnitt 2.3.4). Deshalb definieren wir die Semantik von Ausdrücken hier in der
vollen Allgemeinheit. Die Menge der freien Variablen kann formal durch die Funktion
FV : Aexp ∪ Bexp −→ P(Var), wobei P(Var) die Potenzmenge der Variablenmenge Var
bezeichne, definiert werden:
• FV(n) =df ∅
• FV(x) =df {x}
• FV(a1 + a2 ) =df FV(a1 ) ∪ FV(a2 )
• FV(a1 ∗ a2 ) =df FV(a1 ) ∪ FV(a2 )
• FV(a1 − a2 ) =df FV(a1 ) ∪ FV(a2 )
• FV(true) =df ∅
• FV(false) =df ∅
• FV(a1 == a2 ) =df FV(a1 ) ∪ FV(a2 )
• FV(a1 < a2 ) =df FV(a1 ) ∪ FV(a2 )
• FV(a1 <= a2 ) =df FV(a1 ) ∪ FV(a2 )
• FV(!b) =df FV(b)
• FV(b1 &&b2 ) =df FV(b1 ) ∪ FV(b2 )
Lemma 2.4
Seien σ, σ 0 ∈ Σ mit σ(x) = σ 0 (x) für alle x ∈ FV(a) bzw. x ∈ FV(b) . Dann gilt:
[[ a ]]A (σ) = [[ a ]]A (σ 0 ) bzw. [[ b ]]B (σ) = [[ b ]]B (σ 0 )
3
Beweis
Seien σ, σ 0 ∈ Σ. Wir beweisen die folgende Aussage durch strukturelle Induktion über den
Aufbau des Ausdrucks a:
∀ a ∈ Aexp. (∀ x ∈ FV(a). σ(x) = σ 0 (x)) ⇒ [[ a ]]A (σ) = [[ a ]]A (σ 0 )
Sei a ∈ Aexp. Für alle Teilterme a0 von a gelte die Behauptung, d.h.:
(∀ x ∈ FV(a0 ). σ(x) = σ 0 (x)) ⇒ [[ a0 ]]A (σ) = [[ a0 ]]A (σ 0 )
(IA)
Weiterhin gelte:
∀ x ∈ FV(a). σ(x) = σ 0 (x).
(∗)
2.3. SEMANTIK VON WHILE
53
1. a = n ∈ Num. Dann gilt:
[[ a ]]A (σ) = [[ n ]]A (σ) = [[ n ]]N = [[ n ]]A (σ 0 ) = [[ a ]]A (σ 0 ).
a = x ∈ Var. Dann ist x ∈ FV(a) und es gilt σ(x) = σ 0 (x) nach Annahme (*).
Somit:
[[ a ]]A (σ) = [[ x ]]A (σ) = σ(x) = σ 0 (x) = [[ x ]]A (σ 0 ) = [[ a ]]A (σ 0 ).
2. a = a1 + a2 ∈ Aexp. Dann erhalten wir zunächst:
[[ a ]]A (σ) = [[ a1 + a2 ]]A (σ) = [[ a1 ]]A (σ) + [[ a2 ]]A (σ)
und
[[ a ]]A (σ 0 ) = [[ a1 + a2 ]]A (σ 0 ) = [[ a1 ]]A (σ 0 ) + [[ a2 ]]A (σ 0 ).
Da a1 und a2 unmittelbare Teilausdrücke von a1 + a2 sind und offenbar
FV(a1 ) ⊆ FV(a) sowie FV(a2 ) ⊆ FV(a)
gilt, folgt mit Hilfe der Induktionsannahme (IA):
[[ ai ]]A (σ) = [[ ai ]]A (σ 0 ) für i ∈ {1, 2}
und wegen der Wohldefiniertheit von + die Behauptung.
Für Ausdrücke a1 ∗ a2 und a1 − a2 ist die Argumentation analog.
Per Induktionsprinzip folgt die Behauptung.
Der Beweis der Aussage für Boolesche Ausdrücke geht analog.
2
Der Satz der Kompositionalität und das Substitutionslemma, die wir im Zusammenhang
mit Booleschen Termen kennengelernt haben, können auch für Boolesche und arithmetische Ausdrücke formuliert werden. Wir wollen dies hier exemplarisch für die arithmetischen
Ausdrücke durchführen.
Die syntaktische Substitution ·[·/·] : Aexp × Aexp × Var −→ Aexp für arithmetische
Ausdrücke ist analog zur syntaktischen Substitution für Boolesche Terme definiert: Für
beliebige arithmetische Ausdrücke a, a0 ∈ Aexp und eine beliebigen Variable x ∈ Var
entsteht a[a0 /x] durch (simultanes) Ersetzen aller freier Vorkommen von x in a durch a 0 .
Satz 2.5 (Kompositionalität)
Seien a, a0 , a00 ∈ Aexp mit [[ a0 ]]A = [[ a00 ]]A und x ∈ Var beliebig. Dann gilt
[[ a[a0 /x] ]]A = [[ a[a00 /x] ]]A ,
54
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
d.h. für jeden Zustand σ ∈ Σ gilt:
[[ a[a0 /x] ]]A (σ) = [[ a[a00 /x] ]]A (σ).
3
Lemma 2.6 (Substitutionslemma)
Für beliebige arithmetische Ausdrücke a, a0 ∈ Aexp und jeden Zustand σ ∈ Σ gilt:
[[ a[a0 /x] ]]A (σ) = [[ a ]]A (σ{[[ a0 ]]A (σ)/x}).
3
Sowohl der Kompositionalitätssatz als auch das Substitutionslemma lassen sich wieder
durch strukturelle Induktion (über den induktiven Aufbau arithmetischer Ausdrücke) beweisen (siehe Übungen).
Semantik von Anweisungen
Ziel einer semantischen Modellierung ist eine präzise Erfassung des Effekts, den ein Programm in seiner Programmierumgebung hat. Üblicherweise beschränkt man sich dabei
auf das zugehörige Ein–/Ausgabeverhalten. Wir werden sehen, daß der Effekt bei sequentiellen, imperativen Programmen mit Hilfe von Ein–/Ausgabefunktionen beschrieben
werden kann. Die folgenden drei Abschnitte behandeln drei Standardansätze einer solchen Beschreibung, die operationelle, die denotationelle und die axiomatische Semantik.
Allen drei Ansätzen liegt im wesentlichen die gleiche Beschreibung der Bedeutung elementarer Anweisungen (hier nur die skip–Anweisung und Zuweisungen) zugrunde. Der
Unterschied besteht lediglich in dem ‘Mechanismus der Globalisierung’ auf ganze Programme. Tatsächlich sind die drei Ansätze in dem Sinne konsistent, daß sie Programmen
dieselben Effekte zuordnen.
2.3.2
Operationelle Semantik
Operationelle Semantiken globalisieren ‘argumentweise’, genau der intendierten Berechnung folgend: Zu gegebenen Eingabewerten wird die zugehörige Berechnungsfolge angegeben. Das kann zum einen mit direktem Bezug auf eine abstrakte Maschine, zum
anderen aber auch abstrakter auf der Basis sogenannter Berechnungsfolgen geschehen, die
den Berechnungsvorgang als Folge elementarer Speicherzustandsübergänge beschreiben.
Wir folgen hier der zweiten Variante und entwickeln die strukturierte operationelle
Semantik im Sinne von G. Plotkin.
2.3. SEMANTIK VON WHILE
55
Grundlegende Idee der strukturierten operationellen Semantik ist die sukzessive Transformation von Konfigurationen gemäß der Effekte elementarer Anweisungen. Es gibt zwei
Typen von Konfigurationen:
• hS, σi steht für die Konfiguration einer Programmausführung, in der noch das Programmfragment S auf den Zwischenzustand σ anzuwenden ist.
• σ ist eine finale Konfiguration und charakterisiert das Resultat nach der Terminierung einer Berechnung.
Ein Ableitungsschritt in der strukturierten operationellen Semantik hat die Form hS, σi ⇒
γ mit γ ∈ (Stm × Σ) ∪ Σ (γ ist also eine Konfiguration von einem der beiden oben angegebenen Typen). Im Falle γ ∈ Stm × Σ ist die Berechnung noch nicht abgeschlossen.
γ beschreibt dann die noch verbleibenden Berechnungen. Im anderen Falle ist die Berechnung terminiert.
Die vollständige Definition der möglichen Ableitungsschritte ist durch die nachstehenden
Regeln gegeben. Dabei hat das Regelformat
Prämisse
Nebenbedingung
Konklusion
die folgende Bedeutung: Die Regel Name“ ist anwendbar, wenn die Prämisse und die
”
Nebenbedingungen erfüllt sind. In diesem Fall kann der in der Konklusion beschriebene
Schritt ausgeführt werden. Das Zeichen –“ steht für die leere Prämisse, die immer erfüllt
”
ist. Regeln mit leerer Prämisse nennt man auch Axiome.
[Name]
[asssos ]
[skipsos ]
−
hx = a, σi ⇒ σ{[[ a ]]A (σ)/x}
−
hskip, σi ⇒ σ
[comp1sos ]
hS1 , σi ⇒ hS10 , σ 0 i
hS1 ; S2 , σi ⇒ hS10 ; S2 , σ 0 i
[comp2sos ]
hS1 , σi ⇒ σ 0
hS1 ; S2 , σi ⇒ hS2 , σ 0 i
[iftt
sos ]
−
[[ b ]]B (σ) = tt
hif (b) {S1 } else {S2 }, σi ⇒ hS1 , σi
56
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
[ifffsos ]
[whilesos ]
−
[[ b ]]B (σ) = ff
hif (b) {S1 } else {S2 }, σi ⇒ hS2 , σi
−
hwhile (b) {S}, σi ⇒ hif (b) {S; while (b) {S}} else {skip}, σi
Durch die Hintereinanderausführung von ‘zueinander passenden’ Ableitungsschritten entsteht eine sog. Berechnungsfolge.
Definition 2.7 (Berechnungsfolgen)
Eine Berechnungsfolge zu einer Anweisung S im Startzustand σ ist:
• eine endliche Folge γ0 , . . . , γk von Konfigurationen mit γ0 = hS, σi und γi ⇒ γi+1 für
alle i ∈ {0, . . . , k − 1}.
• eine unendliche Folge γ0 , γ1 , ... von Konfigurationen mit γ0 = hS, σi und γi ⇒ γi+1
für alle i ∈ N.
Wir schreiben γ ⇒i γ 0 , falls es eine Berechnungsfolge der Länge i von γ nach γ 0 gibt und
γ ⇒∗ γ 0 , falls es eine endliche Berechnungsfolge von γ nach γ 0 gibt.
Weiter heißt eine maximale (d.h. nicht weiter verlängerbare) Berechnungsfolge terminierend, falls sie endlich ist, und divergierend, falls sie unendlich ist.
3
Wichtig: Der Berechnungscharakter der While–Schleife“ spiegelt sich bei der struktu”
rierten operationellen Semantik in Iterationen entlang der Berechnungsfolgen wider.
Beispiel 2.8
Sei σ ∈ Σ ein beliebiger Zustand. Für n, m ∈ Z sei σnm der Zustand σ{n/x}{m/y}. Wir
betrachten für das Fakultätprogramm S 00
= x−1 }
y = 1; while (1 < x) { y = y ∗ x ; x
| {z } | {z } | {z }
|
S2
S1
b
|
{z
S0
{z
S
}
}
und den Anfangszustand σ30 die Berechnungsfolge gemäß der Regeln zur strukturierten
2.3. SEMANTIK VON WHILE
57
operationellen Semantik:
hy = 1; while (1 < x) {y = y ∗ x; x = x − 1}, σ 30 i
(a)
=⇒ hwhile (1 < x) {y = y ∗ x; x = x − 1}, σ31 i
(b)
=⇒ hif (1 < x) {y = y ∗ x; x = x − 1; S0 } else {skip}, σ31 i
(c)
=⇒ hy = y ∗ x; x = x − 1; S0 , σ31 i
(d)
=⇒ hx = x − 1; S0 , σ33 i
(e)
=⇒ hwhile (1 < x) {y = y ∗ x; x = x − 1}, σ23 i
(f)
=⇒ hif (1 < x) {y = y ∗ x; x = x − 1; S0 } else {skip}, σ23 i
(g)
=⇒ hy = y ∗ x; x = x − 1; S0 , σ23 i
(h)
=⇒ hx = x − 1; S0 , σ26 i
(i)
=⇒ hwhile (1 < x) {y = y ∗ x; x = x − 1}, σ16 i
(j)
=⇒ hif (1 < x) {y = y ∗ x; x = x − 1; S0 } else {skip}, σ16 i
(k)
=⇒ hskip, σ16 i
(l)
=⇒ σ16
Die Schritte (b), (f) und (j) sind Anwendungen von [while sos ], sie haben keine Prämisse
und brauchen deshalb nicht weiter gerechtfertigt zu werden; ebenso der [skip sos ]-Schritt
ff
(l). Bei (c) und (g) wird [iftt
sos ] und bei (k) [ifsos ] benutzt, auch diese Regeln haben keine
Prämisse, jedoch muß die jeweilige Nebenbedingung den then- bzw. else-Schritt rechtfertigen. Besondere Beachtung verdienen die [comp 1sos ]-Regelanwendungen, deren Rechtfertigung zusammen mit den noch offenen Begründungen nachstehend aufgeführt sind.
58
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
(a) :
(c)
[comp2sos ]
[asssos ]
−
hy = 1, σ3 0i ⇒ σ31
hy = 1; S0 , σ3 0i ⇒ hS 0 , σ31 i
: [[ 1 < x ]]B (σ31 ) = tt
da [[ 1 ]]A (σ31 ) < [[ x ]]A (σ31 ) ⇔ [[ 1 ]]N < σ31 (x) ⇔ 1 < 3
asssos ]
[
(d) : [comp1sos ]
(e)
:
[comp 2sos ]
−
hy = y ∗ x, σ31 i ⇒ σ33
hy = y ∗ x; x = x − 1, σ31 i ⇒ hx = x − 1, σ33 i
hy = y ∗ x; x = x − 1; S0 , σ31 i ⇒ hx = x − 1; S0 , σ33 i
[comp2
sos ]
[asssos ]
−
hx = x − 1, σ33 i ⇒ σ23
hx = x − 1; S0 , σ33 i ⇒ hS 0 , σ23 i
(g) : [[ 1 < x ]]B (σ23 ) = tt
da [[ 1 ]]A (σ23 ) < [[ x ]]A (σ23 ) ⇔ [[ 1 ]]N < σ23 (x) ⇔ 1 < 2
asssos ]
[
(h) : [comp 1sos ]
(i)
:
[comp 2sos ]
−
hy = y ∗ x, σ23 i ⇒ σ26
hy = y ∗ x; x = x − 1, σ23 i ⇒ hx = x − 1, σ26 i
hy = y ∗ x; x = x − 1; S0 , σ23 i ⇒ hx = x − 1; S0 , σ26 i
[comp2
sos ]
[asssos ]
−
hx = x − 1, σ26 i ⇒ σ16
hx = x − 1; S0 , σ26 i ⇒ hS 0 , σ16 i
(k) : [[ 1 < x ]]B (σ16 ) = ff
da nicht gilt: [[ 1 ]]A (σ16 ) < [[ x ]]A (σ16 ) ⇔ [[ 1 ]]N < σ16 (x) ⇔ 1 < 1
3
Eigenschaften der strukturierten operationellen Semantik
Das folgende Lemma zeigt, wie sich die Komposition von Programmen in den Berechnungsfolgen widerspiegelt.
Lemma 2.9
Sei hS1 ; S2 , σi ⇒k σ 00 . Dann gibt es einen Zustand σ 0 und natürliche Zahlen k1 und k2 , so
daß gilt:
• hS1 , σi ⇒k1 σ 0 ,
• hS2 , σ 0 i ⇒k2 σ 00 und
• k = k 1 + k2 .
2.3. SEMANTIK VON WHILE
Beweis
Beweis durch Induktion nach der Länge der Berechnungsfolgen, d.h. hier
59
3
1. Beweis der Eigenschaft für alle Berechnungsfolgen der Länge Null (Induktionsanfang).
2. Beweis der Eigenschaft für alle Berechnungsfolgen der Länge k + 1 unter der Annahme, daß sie für alle Berechnungsfolgen kleinerer Längen richtig ist (Induktionsschritt).
Beweis des Induktionsanfangs:
Im Falle k = 0 ist die Voraussetzung unerfüllbar, also die Behauptung trivial.
Beweis des Induktionsschritts:
Sei nun hS1 ; S2 , σi ⇒k+1 σ 00 . Dann sind zwei Fälle zu unterscheiden:
1. Die Regel [comp1sos ] wurde zur Rechtfertigung des ersten Berechnungsschrittes angewandt.
In diesem Fall läßt sich hS1 ; S2 , σi ⇒ hS10 ; S2 , σ 000 i aus hS1 , σi ⇒ hS10 , σ 000 i ableiten.
Wenden wir nun die Induktionsvoraussetzung auf hS 10 ; S2 , σ 000 i ⇒k σ 00 an, so folgt die
Existenz eines Zustands σ 0 und zweier Zahlen k1 , k2 ∈ N mit
hS10 , σ 000 i ⇒k1 σ 0 , hS2 , σ 0 i ⇒k2 σ 00 und k = k1 + k2 .
Das liefert aber wie gewünscht:
hS1 , σi ⇒k1 +1 σ 0 , hS2 , σ 0 i ⇒k2 σ 00 und k + 1 = (k1 + 1) + k2 .
2. Nur die Regel [comp2sos ] wurde zur Rechtfertigung des ersten Berechnungsschrittes
angewandt.
Dann gilt: hS1 ; S2 , σi ⇒ hS2 , σ 0 i für einen geeigneten Zustand σ 0 . Also gilt die Behauptung für k1 = 1 und k2 = k.
2
Theorem 2.10 (Determiniertheit)
Die strukturierte operationelle Semantik ist deterministisch, d.h.
hS, σi ⇒k1 σ 0 und hS, σi ⇒k2 σ 00 impliziert σ 0 = σ 00 and k1 = k2 .
3
60
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
Der Beweis kann durch verallgemeinerte Induktion über die Länge der Berechnungsfolge k1
geführt werden. Im Induktionsschritt sind abhängig von dem Aufbau von S verschiedene
Möglichkeiten eines ersten Schrittes zu untersuchen. Falls S keine sequentielle Komposition
ist, ist die Argumentation einfach, da der erste Schritt eindeutig festgelegt ist. Ansonsten
betrachten wir S vollständigt zerlegt in seine sequentiellen Komponenten, also in einer
Form S1 ; . . . ; Sn , auf die sukkzessive Lemma 2.9 angewendet werden kann. Mittels wiederholter Anwendung der Induktionsvoraussetzung kann schließlich die Eindeutigkeit der
Berechnungsfolge gezeigt werden. Die exakte Durchführung dieses Beweises sei dem Leser
als Übungsaufgabe überlassen.
Damit können wir jetzt das Semantikschema hStm, Σ− →Σ, [[ · ]] sos i zur strukturierten
operationellen Semantik durch Angabe des folgenden Funktionals vervollständigen.1
Definition 2.11 (Operationelles Semantikfunktional)
Die Funktion [[ · ]]sos : Stm −→ (Σ− →Σ), die durch
(
σ0
falls hS, σi ⇒∗ σ 0
[[ S ]]sos (σ) =
undefiniert sonst
definiert ist, heißt semantisches Funktional zur strukturierten operationellen Semantik.
3
Das semantische Funktional zur strukturierten operationellen Semantik weist jeder Anweisung eine partielle Funtion von der Menge der Zustände in die Menge der Zustaände,
eine sogenannte partielle Zustandstransformation, zu.
Bemerkung 2.12
Die Wohldefiniertheit des semantischen Funktionals [[ · ]] sos folgt unmittelbar aus Theorem 2.10.
3
Die Rechtfertigung des Begriffs strukturierte operationelle Semantik liefert der folgende
Satz:
Satz 2.13 (Kompositionalität der strukturierten operationellen Semantik)
Sei [[ Si ]]sos = [[ Si0 ]]sos für beliebige Si , Si0 ∈ Stm und i ∈ {1, 2}. Dann gilt:
1. [[ S1 ; S2 ]]sos = [[ S10 ; S20 ]]sos
2. [[ if (b) {S1 } else {S2 } ]]sos = [[ if (b) {S10 } else {S20 } ]]sos
3. [[ while (b) {S1 } ]]sos = [[ while (b) {S10 } ]]sos
3
1
Dabei bezeichnet ’− →’ eine partiell definierte Funktion, also eine, die nicht an jeder Argumentposition
einen definierten Funktionswert besitzen muss.
2.3. SEMANTIK VON WHILE
2.3.3
61
Denotationelle Semantik
In diesem Abschnitt soll der Effekt von Programmen losgelöst von deren konkretem Laufzeitverhalten definiert werden. D.h. anders als im operationellen Fall, wo das zugehörige
Semantikschema entlang der schrittweisen Programmausführung definiert wurde, soll hier
eine der syntaktischen Struktur des Programms folgende Definition entwickelt werden.
Diesem Ansatz liegt die Beobachtung zugrunde, daß das semantische Funktional [[ · ]] sos
nicht nur so abstrakt wie möglich (d.h. genau auf die Essenz (Effekt) beschränkt ist – eine
typische Eigenschaft operationeller Semantiken), sondern gleichzeitig auch kompositionell
ist (siehe Satz 2.13).
Kern der denotationellen Semantik ist also die direkte strukturierte Definition eines Semantikfunktionals
[[ · ]]ds : Stm −→ (Σ− →Σ).
Definieren wir die semantische bedingte Anweisung“ durch
”
cond : (Σ → B) × (Σ− →Σ)2 −→ (Σ− →Σ),
mittels
cond(p, g1 , g2 )(σ) =
(
g1 (σ)
g2 (σ)
falls p(σ) = tt
.
sonst
so kann [[ · ]]ds induktiv wie folgt definiert werden:
• [[ x = a ]]ds (σ) =df σ{[[ a ]]A (σ)/x}
• [[ skip ]]ds =df id (Identität auf den Zustandstransformationen)
• [[ S1 ; S2 ]]ds =df [[ S2 ]]ds ◦ [[ S1 ]]ds
• [[ if (b) {S1 } else {S2 } ]]ds =df cond([[ b ]]B , [[ S1 ]]ds , [[ S2 ]]ds )
• [[ while (b) {S} ]]ds =df fix Φ, wobei
Φ(g) =df cond([[ b ]]B , g ◦ [[ S ]]ds , id)
Die ersten vier definierenden Gleichnungen spiegeln direkt die übliche (operationelle) Intuition wider. Interessant und neu ist die fünfte Gleichung, deren neue ‘Ingredienz’ fix im
folgenden motiviert, präzisiert und fundiert werden soll.
Hier steht fix für Fixpunkt“. Die Betrachtung eines Fixpunkts in diesem Zusammenhang
”
ist durch folgende Reduktion“ von while“ auf if () {} else {}“ mit Hilfe des bereits
”
”
”
‘etablierten’ Teils der Definition von [[ · ]] ds motiviert:
[[ while (b) {S} ]]ds = cond([[ b ]]B , [[ while (b) {S} ]]ds ◦ [[ S ]]ds , id)
62
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
[[ while (b) {S} ]]ds muß also ein Fixpunkt des Funktionals
Φ : (Σ− →Σ) −→ (Σ− →Σ)
sein, d.h.
Φ([[ while (b) {S} ]]ds ) = [[ while (b) {S} ]]ds .
Leider reicht diese Erkenntnis noch nicht aus, um ein wohldefiniertes Funktional zu erhalten, denn
• es kann mehrere Fixpunkte geben, z.B. hat das Funktional Φ 1 (g) = g (Identität)
jede Zustandstransformation g 0 : Σ− →Σ als Fixpunkt.
• es braucht gar keine Fixpunkte zu geben, z.B. hat
(
g1 falls g = g2
Φ2 (g) =
g2 sonst
für g1 6= g2 keine Fixpunkte.
Dieses Dilemma wird wie folgt behoben:
• die Mehrdeutigkeit durch die Auswahl eines speziellen Fixpunktes.
• die Existenz durch strukturelle Einschränkungen bei der Definition des Funktionals
Φ (vgl. elementare Fixpunkttheorie).
Um zu erklären, was den ‘intendierten’ Fixpunkt fix Φ gegenüber anderen Fixpunkten von
Φ auszeichnet, betrachten wir zunächst einen beliebigen Fixpunkt g0 von Φ. Bei gegebenem
Anfangszustand σ0 können bei einer while–Schleife while (b) {S} drei Fälle auftreten:
1. Es gibt Zustände σ1 , . . . , σn , n ≥ 0, derart, daß
(
tt falls i ∈ {0, ..., n − 1}
[[ b ]]B (σi ) =
ff falls i = n
und
[[ S ]]ds (σi ) = σi+1 für i ∈ {0, . . . , n − 1}
Das entspricht dem erfolgreichen Terminieren der while–Schleife nach n − 1 Schleifendurchläufen.
2. Es gibt Zustände σ0 , . . . , σn , n ≥ 0, derart, daß [[ b ]] B (σi ) = tt für alle i ∈ {0, . . . , n}
und
(
σi+1
falls i ∈ {0, ..., n − 1}
[[ S ]]ds (σi ) =
undefiniert sonst
2.3. SEMANTIK VON WHILE
63
Das entspricht der Situation, in der der Schleifenrumpf in der n–ten Iteration divergiert. Damit dies geschehen kann, muß S noch eine weitere while–Schleife enthalten,
die nun divergiert. Die Divergenz findet also nicht auf dem betrachteten Schleifenniveau statt.
3. Es gibt Zustände σ1 , σ2 , σ3 , ... derart, daß [[ b ]] B (σi ) = tt für alle i ∈ N und
[[ S ]]ds (σi ) = σi+1 für alle i ∈ N
Das entspricht der Situation, in der die betrachtete Schleife divergiert.
Im ersten Fall erhalten wir nach Definition von Φ für i ∈ {0, . . . , n − 1} und einen beliebigen Fixpunkt g0 von Φ:
(Φ(g0 ))(σi ) = g0 ◦ [[ S ]]ds (σi ) = g0 (σi+1 ) und
(Φ(g0 ))(σn ) = id(σn ) = σn .
Da g0 aber ein Fixpunkt ist, folgt weiter für i ∈ {0, . . . , n − 1}:
g0 (σi ) = g0 (σi+1 ) und g0 (σn ) = σn
und damit insbesondere g0 (σ0 ) = σn .
Folglich stimmen alle Fixpunkte auf Argumenten s 0 dieser ersten Kategorie überein.
Im zweiten Fall erhalten wir ähnlich:
(Φ(g0 ))(σi ) = g0 (σi+1 ) für i ∈ {0, . . . , n − 1} und
(Φ(g0 ))(σn ) = undefiniert.
Damit folgt aus der Fixpunkteigenschaft von g 0 , daß g0 (σ0 ) undefiniert ist. Insbesondere
gleichen sich alle Fixpunkte auch auf Argumenten der zweiten Kategorie.
Mögliche Unterschiede zwischen Fixpunkten ergeben sich nur in der dritten Kategorie.
Hier erhalten wir g0 (σ0 ) = g0 (σi ) für alle i ∈ N. Das legt den Wert g0 (σ0 ) aber nicht fest.
Dieses Phänomen ist nicht überraschend, da das zu while (true) {skip}“ gehörige Funk”
tional Φ offenbar die identische Funktion ist, die jedes Element auf sich selbst abbildet.
Auf der anderen Seite verlangt das intuitive (bzw. operationelle) Verständnis, daß
[[ while (true) {skip} ]]ds
undefiniert ist. Auf die allgemeine Situation übertragen bedeutet das:
64
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
fix Φ ist der am wenigsten definierte Fixpunkt von Φ.
Für unsere theoretischen Betrachtungen im Rahmen der Fixpunkttheorie muß diese Charakterisierung umformuliert werden. Dazu definieren wir zwischen partiellen (d.h. nicht
überall definierten) Funktionen g1 , g2 : Σ− →Σ eine Ordnungsbeziehung, g1 g2 , falls
• g2 überall dort definiert ist, wo g1 definiert ist, und sich
• g1 und g2 auf dem gemeinsamen Definitionsbereich gleichen.
Das ist äquivalent dazu, daß der Graph graph(g 1 ) von g1 im Graphen graph(g2 ) von g2
enthalten ist. Formal läßt sich das wie folgt schreiben:
g1 g2 genau dann, wenn graph(g1 ) ⊆ graph(g2 ),
wobei graph(g) ⊆ Σ × Σ mit (σ, σ 0 ) ∈ graph(g), falls g(σ) = σ 0 .
Anschaulich gilt g1 g2 also genau dann, wenn g1 höchstens an den Stellen definiert ist, an
denen g2 definiert ist, und die partiellen Funktionen g 1 und g2 an allen Stellen, an denen
beide definiert sind, denselben Funktionswert besitzen.
Natürlich ist diese Ordnung nicht total, wie das folgende Beispiel zeigt.
Beispiel 2.14
Seien g1 , g2 , g3 wie folgt definiert:
(
σ
undefiniert
falls σ(x) gerade ist
sonst
σ
undefiniert
falls σ(x) ungerade ist
sonst
g1 (σ)
=df
g2 (σ)
=df
(
g3 (σ)
=df
σ
Dann gilt: g1 g3 und g2 g3 . Aber es gilt weder g1 g2 noch g2 g1 .
3
Mit Hilfe dieser Ordnung auf partiellen Funktionen können wir fix Φ wie folgt charakterisieren:
• fix Φ ist ein Fixpunkt von Φ, d.h. fix Φ = Φ(fix Φ).
• fix Φ ist der kleinste Fixpunkt von Φ, d.h.
∀g0 .
Φ(g0 ) = g0 impliziert fix Φ g0 .
Diese Charakterisierung impliziert automatisch die Eindeutigkeit von fix Φ. Der folgende
Abschnitt widmet sich nun der noch fehlenden Existenzgarantie.
2.3. SEMANTIK VON WHILE
65
Elementare Fixpunkttheorie
Zum Existenznachweis entwickeln wir nun die sogenannte ‘elementare Fixpunkttheorie’.
Das erfordert zunächst die Einführung einiger neuer Begriffe:
Definition 2.15 (Halbordnung)
Sei D eine beliebige Menge.
a) Ist vD eine Relation auf D derart, daß für alle d1 , d2 , d3 ∈ D gilt:
– d1 vD d1 (Reflexivität),
– d1 vD d2 ∧ d2 vD d3 impliziert d1 vD d3 (Transitivität) und
– d1 vD d2 ∧ d2 vD d1 impliziert d1 = d2 (Antisymmetrie),
so heißt vD Halbordnungsrelation auf D.
b) Ein Paar hD; vD i bestehend aus einer Menge D und einer Halbordnungsrelation v D
heißt Halbordnung. Im folgenden wird der Index D weggelassen, falls er aus dem
Kontext hervorgeht.
3
Beispiel 2.16
hN; ≤i ist eine Halbordnung, aber hN; <i ist keine Halbordnung.
3
Das folgende Lemma etabliert den Bezug der neuen Definition zu WHILE. Hier bezeichne
Σ− →Σ die Menge der partiellen Funktionen von Σ in sich, also die Menge aller Zustandstransformationen. Der Beweis des Lemmas ist eine leichte Übungsaufgabe.
Lemma 2.17
hΣ− →Σ; i ist eine Halbordnung.
3
Definition 2.18 (Kette, Obere Schranke,Supremum)
Sei hD; vD i eine Halbordnung und T ⊆ D.
1. T heißt Kette in D, falls für alle d1 , d2 ∈ T gilt:
d1 vD d2 oder d2 vD d1 .
2. Ein Element d ∈ D heißt obere Schranke von T , falls für alle d0 ∈ T gilt: d0 vD d.
3. Eine obere Schranke d von T heißt kleinste obere Schranke (Supremum) von
T (in D), falls d kleiner (vD ) als alle anderen oberen Schranken von T ist.
3
66
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
Beispiel 2.19
• Jede Teilmenge T ⊆ N ist eine Kette in hN; ≤i.
• Jede endliche Teilmenge T ⊆ N besitzt eine (kleinste) obere Schranke in hN; ≤i.
3
Sei T eine Kette in einer Halbordnung hD; vi. T besitze eine kleinste obere Schranke.
F
Dann bezeichnen wir diese kleinste obere Schranke mit D T . Falls aus dem Kontext
unmißverständlich ersichtlich, wird der Index D weggelassen.
Definition 2.20 (Kettenvollständigkeit)
Eine Halbordnung heißt kettenvollst ändig, falls jede ihrer Ketten eine kleinste obere
Schranke besitzt. Solche Halbordnung bezeichnen wir als CCPOs (= Chain Complete
Partial Orders).
3
Beispiel 2.21
hP(N); ⊆i ist eine CCPO.
hN; ≤i ist keine CCPO, da N keine kleinste obere Schranke in N besitzt.
3
Lemma 2.22
hΣ− →Σ; i ist eine CCPO. Die kleinste obere Schranke zu einer Kette T in dieser CCPO
ist gegeben durch:
G
[
graph( T ) = {graph(g) | g ∈ T }.
3
Beweis
F
S
Nach Lemma 2.17 muß nur noch die Gleichung graph( T ) = {graph(g) | g ∈ T } etabliert werden.
Dazu beweisen wir zunächst, daß
g0 : Σ− →Σ ist, d.h.
(σ, σ 0 ), (σ, σ 00 ) ∈
Seien hierzu (σ, σ 0 ), (σ, σ 00 ) ∈
und g2 in T mit
S
S
[
{graph(g) | g ∈ T } der Graph einer partiellen Funktion
{graph(g) | g ∈ T } impliziert σ 0 = σ 00 .
{graph(g) | g ∈ T }. Dann gibt es partielle Funktionen g 1
• (σ, σ 0 ) ∈ graph(g1 ) und
• (σ, σ 00 ) ∈ graph(g2 ).
Da T aber eine Kette ist, gilt entweder g 1 g2 oder g2 g1 . Das liefert aber in beiden
Fällen wie gewünscht σ 0 = g1 (σ) = g2 (σ) = σ 00 .
2.3. SEMANTIK VON WHILE
67
S
Sei also g0 die durch {graph(g) | g ∈ T } definierte partielle Funktion, d.h. graph(g 0 ) =
S
{graph(g) | g ∈ T }. Dann ist g0 offensichtlich eine obere Schranke von T , da graph(g 0 )
nach Definition sämtliche Graphen graph(g) zu Elementen g von T enthält.
Es bleibt also nur noch zu zeigen, daß g 0 kleinst“ ist. Sei also g1 eine beliebige obere
”
Schranke von T . Dann gilt aber graph(g) ⊆ graph(g 1 ) für alle g ∈ T und damit
graph(g0 ) =
[
{graph(g) | g ∈ T } ⊆ graph(g1 ).
2
Existenz von fix Φ
In diesem Abschnitt zeigen wir die Existenz von fix Φ in CCPOs. Basierend auf Lemma 2.22 wird das die Wohldefiniertheit von [[ · ]] ds implizieren.
Definition 2.23 (Monotonie)
Seien hD; vi und hD 0 ; v0 i zwei CCPOs und f : D −→ D 0 eine (totale) Funktion. Dann
heißt f monoton, falls für alle d1 , d2 ∈ D gilt:
d1 v d2 impliziert f (d1 ) v0 f (d2 ).
3
Insbesondere ist also jede konstante Funktion und die Identität monoton.
Lemma 2.24
Seien hD; vi und hD 0 ; v0 i CCPOs, f : D −→ D 0 eine beliebige Funktion und T ⊆ D eine
Kette in D. Dann gilt:
1. Ist f monoton, so ist {f (d) | d ∈ T } eine Kette in D 0 .
2. Ist f monoton, so ist
F
{f (d) | d ∈ T } v0 f (
F
T ).
3
Beweis
Zum Beweis der ersten Aussage seien d 01 , d02 ∈ {f (d) | d ∈ T }. Dann gibt es d1 , d2 ∈ T mit
f (d1 ) = d01 und f (d2 ) = d02 . Da T eine Kette in D ist, können wir o.B.d.A. davon ausgehen,
daß d1 v d2 gilt. Das liefert aber wegen der Monotonie von f
d01 = f (d1 ) v0 f (d2 ) = d02
68
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
F
Zum Beweis der zweiten Aussage sei d ∈ T . Wegen d v
T und der Monotonie von f
F
F
0
liefert das unmittelbar f (d) v f ( T ). Also ist f ( T ) obere Schranke von {f (d) | d ∈ T }
F
und damit f (d) v0 f ( T ).
2
Bemerkung 2.25
F
Ist hD; vi eine CCPO, so existiert insbesondere ⊥ = df
∅. Weiterhin ist ⊥ das kleinste
Element in D, d.h. ∀ d ∈ D. ⊥ v d. Dieses folgt unmittelbar daraus, daß die leere Menge ∅
offensichtlich eine Kette ist und jedes Element von D eine obere Schranke von ∅ ist. Daher
ist das Supremum von ∅ gleichzeitig das kleinste Element von D.
3
Definition 2.26 (Stetigkeit)
Seien hD; vi und hD 0 ; v0 i zwei CCPOs und f : D −→ D 0 . f heißt stetig, falls f monoton
ist und für jede nicht leere Kette T in hD; vi gilt:
G
G
f( T ) =
{f (d) | d ∈ T }.
3
Lemma 2.27
Mit f und f 0 ist auch f 0 ◦ f stetig.
3
Beweis
Seien hD; vi, hD 0 ; v0 i und hD 00 ; v00 i Halbordnungen, sowie f : D −→ D 0 und f 0 : D 0 −→ D 00
stetige Funktionen. Offenbar ist f 0 ◦ f monoton, denn mit der Monotonie von f und f 0
gilt für d1 , d2 ∈ D:
d1 v d2 ⇒ f (d1 ) v0 f (d2 ) ⇒ (f 0 ◦ f )(d1 ) v00 (f 0 ◦ f )(d2 ).
Sei weiter T eine nichtleere Kette in D. Da f stetig ist, ist f insbesondere monoton. Also
ist {f (d) | d ∈ T } nach Lemma 2.24 eine Kette in D 0 . Also folgt aus der Stetigkeit von f :
G
G
f( T ) =
{f (d) | d ∈ T }(∗)
Da aber auch f 0 stetig ist, folgt analog
{f 0 (d0 ) | d0 ∈ {f (d) | d ∈ T }} = {f 0 (f (d)) | d ∈ T }
ist eine Kette in D 00 , und weiter:
F
F 0 0
f 0 ( {f (d) | d ∈ T } =
{f (d ) | d0 ∈ {f (d) | d ∈ T }}
=
Nach (∗) gilt somit
f 0 (f (
G
T )) =
F
{f 0 (f (d)) | d ∈ T }
G
{f 0 (f (d)) | d ∈ T }
2.3. SEMANTIK VON WHILE
69
und damit wie gewünscht
(f 0 ◦ f )(
G
T) =
G
{(f 0 ◦ f )(d) | d ∈ T },
2
Damit läßt sich nun der Hauptsatz dieses Abschnitts beweisen:
Satz 2.28 (Fixpunktsatz von Knaster/Tarski bzw. Kleene)
Sei hD; vi eine kettenvollständige Halbordnung und f : D −→ D eine stetige Funktion.
Dann gilt:
G
{f n (⊥) | n ∈ N}
ist der kleinste Fixpunkt von f in D. Wir bezeichnen ihn mit fixf .
3
Beweis
Der Beweis etabliert nacheinander die folgenden drei Eigenschaften von fixf :
1. die Wohldefiniertheit
2. die Fixpunkteigenschaft und
3. die Kleinstheit“.
”
F
Offenbar is fixf = {f n (⊥) | n ∈ N} wohldefiniert, falls {f n (⊥) | n ∈ N} eine nichtleere
Kette in hD; vi ist. Seien also d, d0 ∈ {f n (⊥) | n ∈ N}. Dann müssen wir zeigen, daß d
und d0 in unserer Ordung vergleichbar sind. Zunächst liefert uns die Wahl von d und d 0
natürliche Zahlen k und l mit:
d = f k (⊥) und d0 = f l (⊥)
Sein nun o.B.d.A. k ≤ l. Dann gilt d0 = f m+k (⊥) für m = l − k.
Andererseits ist ⊥ das kleinste Element von D. Also gilt insbesondere ⊥ v f m (⊥). Außerdem folgt aus Lemma 2.27 durch eine triviale Induktion, daß mit f auch f k stetig, und
damit insbesondere monoton ist. Also gilt wie gewünscht:
d = f k (⊥) v f k (f m (⊥)) = f m+k (⊥)) = d0
70
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
Die Fixpunkteigenschaft von fixf ist Konsequenz folgender Gleichungskette:
f (fixf )
(Definition von fixf )
(Stetigkeit von f )
F
= f ( {f n (⊥) | n ∈ N})
=
=
( ∅ v d für alle d ∈ D ) =
F
(Definition von fixf )
F
F
F
{f (f n (⊥)) | n ∈ N}
{f n (⊥) | n ∈ N\{0}}
{f n (⊥) | n ∈ N}
= fixf
Um zu zeigen, daß fixf der kleinste Fixpunkt von f ist, betrachten wir einen beliebigen
Fixpunkt d ∈ D von f . Da ⊥ das kleinste Element von D ist, folgt für diesen aus der
Monotonie von f
∀n ∈ N. f n (⊥) v f n (d) = d
Also ist d eine obere Schranke von {f n (⊥) | n ∈ N}, womit wir wie gewünscht fixf =
F n
{f (⊥) | n ∈ N} v d erhalten.
2
Anwendung auf WHILE
Es ist unser nächstes Ziel, zu beweisen, daß
[[ while (b) {S} ]]ds = fix Φb, S
mit Φb, S (g) = cond([[ b ]]B , g ◦ [[ S ]]ds , id) eine wohldefinierte Funktion ist. Nach Lemma 2.22 ist hΣ− →Σ; i eine CCPO. Also muß nur noch gezeigt werden, daß Φ b, S stetig ist.
Dazu zerlegen wir Φb, S in die Funktionen Φb1 (g) = cond([[ b ]]B , g, id) und ΦS2 (g) = g◦[[ S ]]ds .
Tatsächlich gilt:
Φb, S (g) = Φb1 (ΦS2 (g))
Wegen Lemma 2.27 genügt es also zu zeigen, daß Φb1 und ΦS2 stetig sind. Für Φb1 ist das
eine Konsequenz aus folgendem generelleren Resultat:
Lemma 2.29
p,g0
0
Sei g0 : Σ− →Σ, p : Σ −→ B und Φp,g
stetig.
1 (g) = cond(p, g, g0 ). Dann ist Φ1
Beweis
0
Die Monotonie von Φp,g
1 , d.h.
p,g0
0
∀ g1 , g2 : Σ− →Σ. g1 g2 impliziert (Φp,g
1 (g1 )) (Φ1 (g2 ))
3
2.3. SEMANTIK VON WHILE
71
folgt aus folgender einfachen Fallunterscheidung:
p,g0
0
1. p(σ) = tt: Dann ist (Φp,g
1 (g1 ))(σ) = g1 (σ) g2 (σ) = (Φ1 (g2 ))(σ).
p,g0
0
2. p(σ) = ff : Hier ist (Φp,g
1 (g1 ))(σ) = g0 (σ) = (Φ1 (g2 ))(σ).
Damit reduziert Lemma 2.24 das Problem darauf,
G
G p,g
0
Φp,g
(
T
)
{Φ1 0 (g) | g ∈ T }
1
oder gleichbedeutend (Lemma 2.22)
G
G p,g
0
graph(Φp,g
(
T
))
⊆
graph(
{Φ1 0 (g) | g ∈ T })
1
F
0
für jede nichtleere Kette T in Σ− →Σ zu zeigen. Sei also (σ, σ 0 ) ∈ graph(Φp,g
T ))
1 (
F
0
0 . Dann genügt es offenbar, die Existenz eines
oder gleichbedeutend (Φp,g
(
T
))(σ)
=
σ
1
0
0
Elements g ∈ T mit (Φp,g
1 (g))(σ) = σ nachzuweisen. Im Falle p(σ) = ff ist dies trivial,
0
0
0
da dort (Φp,g
1 (g))(σ) = g0 (σ) = σ für alle g ∈ T gilt. Sei also p(σ) = tt. Dann folgt σ =
F
F
F
0
T ))(σ) = ( T )(σ) und damit (σ, σ 0 ) ∈ graph( T ). Da aber nach Lemma 2.22
(Φp,g
1 (
F
S
graph( T ) = {graph(g) | g ∈ T } gilt, muß ein Element g ∈ T existieren mit g(σ) = σ 0 ,
0
0
2
also mit (Φp,g
1 (g))(σ) = σ .
Die Stetigkeit von ΦS2 ist Konsequenz des folgenden Resultats:
Lemma 2.30
Sei g0 : Σ− →Σ eine beliebige Zustandstransformation und Φ g20 (g) = g ◦ g0 . Dann ist Φg20
stetig.
3
Beweis
Zum Beweis dieses Lemmas benötigen wir das Relationenprodukt ◦ R , daß für zwei binäre
Relationen R1 und R2 wie folgt definiert ist:
R1 ◦R R2 =df {(x, y) | ∃ z. (x, z) ∈ R1 ∧ (z, y) ∈ R2 }.
Für den Nachweis der Monotonie von Φg20 seien g1 , g2 ∈ Σ− →Σ mit g1 g2 . Dann gilt:
graph (Φg20 (g1 ))
(Definition von Φg20 )
= graph (g1 ◦ g0 )
(Definition von ◦R )
= graph (g0 ) ◦R graph(g1 )
(g1 g2 , Definition von ◦R ) ⊆ graph (g0 ) ◦R graph(g2 )
(Definition von ◦R )
= graph (g2 ◦ g0 )
(Definition von Φg20 )
= graph (Φg20 (g2 ))
72
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
Also haben wir Φg20 (g1 ) Φg20 (g2 ). Ähnlich folgt die Stetigkeit von Φg20 aus folgender, für
jede nichtleere Kette T in Σ− →Σ gültigen Gleichungskette:
graph (Φg20 (
(Definition von Φg20 ) = graph ((
F
F
T ))
T ) ◦ g0 )
(Definition von ◦R )
= graph (g0 ) ◦R graph(
(Lemma 2.22)
= graph (g0 ) ◦R
=
=
(Definition von Φg20 ) =
(Lemma 2.22)
S
S
S
S
F
T)
{graph(g) | g ∈ T }
{graph(g0 ) ◦R graph(g) | g ∈ T }
{graph(g ◦ g0 ) | g ∈ T }
{graph(Φg20 (g)) | g ∈ T }
= graph (
F
{Φg20 (g) | g ∈ T })
2
Zusammenfassend erhalten wir als Konsequenz aus den Lemmata 2.29 und 2.30:
Korollar 2.31
Φb, S ist stetig.
3
Damit ist die Hauptarbeit für das folgende Resultat geleistet:
Theorem 2.32 (Wohldefiniertheit)
Das semantische Funktional [[ · ]]ds : Stm −→ (Σ− →Σ) zur denotationellen Semantik ist
wohldefiniert.
3
Beweis
Es genügt zu zeigen, das die semantischen Funktionen [[ S ]] ds : Σ− →Σ für alle WHILE–
Programme S wohldefiniert sind. Der folgende Beweis ist induktiv über den syntaktischen
Aufbau des betrachteten Programms S:
• [x = a]: trivial.
• [skip]: trivial.
• [S1 ; S2 ]: Nach Induktionsvoraussetzung sind [[ S 1 ]]ds und [[ S2 ]]ds wohldefiniert. Also
ist auch die Komposition wohldefiniert.
2.3. SEMANTIK VON WHILE
73
• [if (b) {S1 } else {S2 }]: Nach Definition ist
[[ if (b) {S1 } else {S2 } ]]ds = cond([[ b ]]B , [[ S1 ]]ds , [[ S2 ]]ds ).
Die Behauptung folgt nun aus der Wohldefiniertheit von cond und der Induktionsvoraussetzung für S1 und S2 .
• [while (b) {S}]: Nach Induktionsvoraussetzung ist [[ S ]] ds eine wohldefinierte Funktion. Außerdem ist Φb, S nach Korollar 2.31 stetig. Damit können wir den Fixpunktsatz 2.28 anwenden und erhalten, daß fix Φ b, S existiert und deshalb auch
[[ while (b) {S} ]]ds
wohldefiniert ist.
2
Beispiel 2.33
Wir betrachten das folgende Programm zur ganzzahligen Division mit Rest:
S0
S =df
z
}|
{
z = 0; while (y ≤ x) {z = z + 1; x = x − y}
|
{z
}
SW
Dann gilt nach Definition des denotationellen Semantikfunktionals:
[[ S ]]ds = [[ SW ]]ds ◦ [[ z = 0 ]]ds
oder gleichbedeutend
[[ S ]]ds (σ) = [[ SW ]]ds ◦ [[ z = 0 ]]ds (σ) = [[ SW ]]ds (σ{0/z})
Die Semantik der While–Schleife erfordert die Berechnung eines kleinsten Fixpunkts. Diesen berechnen wir mit Hilfe der im Fixpunktsatz von Knaster/Tarski implizit angegebenen
Iterationsvorschrift. Zunächst ist dazu das Funktional Φb, S zu bestimmen. Hier gilt:
[[ SW ]]ds = fixΦb, S mit
(
g ◦ [[ S 0 ]]ds (σ) , falls σ(y) ≤ σ(x)
b,
S
(Φ (g))(σ) =
σ
, falls σ(y) > σ(x)
(sonst)
(
g(σ{σ(z) + 1/z}{σ{σ(z) + 1/z}(x) − σ{σ(z) + 1/z}(y)/x}) , falls σ(y) ≤ σ(x)
(?)
=
σ
, falls σ(y) > σ(x)


+ 1/z}{σ(x) − σ(y)/x} ) , falls σ(y) ≤ σ(x)
 g(σ{σ(z)
|
{z
}
=
(?)
σ1

 σ
, falls σ(y) > σ(x)
74
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
Berechne nun einige Iterationen gemäß dem Fixpunktsatz:
0
n = 0 : (Φb, S (⊥))(σ) = ⊥(σ) = undefiniert(⊥)
1
n = 1 : (Φb, S (⊥))(σ)
eingesetzt in (?)
mit g = ⊥
2
=
(
⊥(σ1 ) = ⊥ , falls σ(y) ≤ σ(x)
(??)
σ
, falls σ(y) > σ(x)
1
n = 2 : (Φb, S (⊥))(σ) = Φb, S (Φb, S (⊥))(σ)
(
1
Φb, S (⊥)(σ1 ) , falls σ(y) ≤ σ(x)
eingesetzt in (?)
=
σ
, falls σ(y) > σ(x)
mit g = Φ(⊥)
)
 (

⊥(σ2 ) = ⊥ , falls σ1 (y) ≤ σ1 (x)

, falls σ(y) ≤ σ(x)
=
(??)
eingesetzt
σ1
, falls σ1 (y) > σ1 (x)


für
Φ1 (⊥)(σ1 ).
σ
, falls σ(y) > σ(x)
Dabei: σ in (??)
ist jetzt σ1
mit σ2 = σ1 {σ1 (z) + 1/z}{σ1 (x) − σ1 (y)/x}
Es ist σ1 (y) = σ(y) und σ1 (x) = σ(x) − σ(y). Daher gilt:


 ⊥ , falls σ(y) ≤ σ(x) − σ(y) und falls σ(y) ≤ σ(x)
=
σ1 , falls σ(y) > σ(x) − σ(y) und falls σ(y) ≤ σ(x)


σ , falls σ(y) > σ(x)


 ⊥ , falls σ(x) ≥ 2σ(y)
=
σ1 , falls σ(y) ≤ σ(x) < 2σ(y) (? ? ?)


σ , falls σ(x) < σ(y)
3
2
n = 3 : (Φb, S (⊥))(σ) = Φb, S (Φb, S (⊥))(σ)
(
2
Φb, S (⊥)(σ1 ) , falls σ(y) ≤ σ(x)
eingesetzt in (?)
=
σ
, falls σ(y) > σ(x)
mit g = Φ2 (⊥)
 


 ⊥ , falls σ1 (x) ≥ 2σ1 (y)



σ2 , falls σ1 (y) ≤ σ1 (x) < 2σ1 (y)

(? ? ?) eingesetzt
=


σ1 , falls σ1 (x) < σ1 (y)


für Φ2 (⊥)(σ1 ).

σ
Dabei ist σ in (? ?
?) jetzt σ1





, falls σ(y) ≤ σ(x)
, falls σ(y) > σ(x)
2.3. SEMANTIK VON WHILE

⊥ ,



 σ ,
2
=

σ1 ,



σ ,

⊥ ,



 σ ,
2
=
 σ1 ,



σ ,
i
75
falls
falls
falls
falls
σ(x) − σ(y) ≥ 2σ(y) und falls σ(y) ≤ σ(x)
σ(y) ≤ σ(x) − σ(y) < 2σ(y) und falls σ(y) ≤ σ(x)
σ(x) − σ(y) < σ(y) und falls σ(y) ≤ σ(x)
σ(y) > σ(x)
falls
falls
falls
falls
σ(x) ≥ 3σ(y)
2σ(y) ≤ σ(x) < 3σ(y)
σ(y) ≤ σ(x) < 2σ(y)
σ(y) > σ(x)
i−1
n = i : (Φb, S (⊥))(σ) = Φb, S (Φb, S (⊥))(σ)


, falls σ(x) ≥ iσ(y)
 ⊥




 σi−1 , falls (i − 1)σ(y) ≤ σ(x) < iσ(y)


 ..
.
=

σ2
, falls 2σ(y) ≤ σ(x) < 3σ(y)





σ1
, falls σ(y) ≤ σ(x) < 2σ(y)




σ
, falls σ(x) < σ(y)
Der Fixpunkt fixΦb, S ergibt sich jetzt als folgende unendliche Fallunterscheidung:

..


.






 σi−1 , falls (i − 1)σ(y) ≤ σ(x) < iσ(y)
..
b,
S
(fixΦ )(σ) =
.



..


.



 σ
, falls σ(x) < σ(y)
Beachte: Der ⊥-Fall ist in der obigen Fallunterscheidung nicht mehr vorhanden.
mit σk = σk−1 {σk−1 (z) + 1/z}{σk−1 (x) − σk−1 (y)/x}
=



















..
.
σ{σ(z) + (i − 1)/z}{σ(x) − (i − 1)σ(y)/x}
..
.
..
.
σ
, falls
(i − 1)σ(y) ≤ σ(x) < iσ(y)
, falls
σ(x) < σ(y)
Für die denotationelle Semantik von S bzgl. σ erhalten wir folglich:
76
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
[[ S ]]ds (σ) = [[ SW ]]ds (σ{0/z}) = fixΦb, S (σ{0/z})

..


 .
=
σ{k − 1/z}{σ(x) − (k − 1)σ(y)/x} , falls (k − 1)σ(y) ≤ σ(x) < kσ(y)


 ..
.
= σ{σ(x) DIV σ(y)/z}{σ(x) MOD σ(y)/x}
3
Beispiel 2.34
Als zweites Beispiel betrachten wir das Fakultätprogramm:
S =df y = 1; while (1 < x) {y = y ∗ x; x = x − 1}.
Sei zur Abkürzung b =df 1 < x und S 0 =df y = y ∗ x; x = x − 1. Hier gilt für einen
beliebigen Startzustand σ0 :
[[ S ]]ds (σ0 ) = (fix Φb, S )(σ) für σ = σ0 {1/y}
Dann ist Φ =df Φb, S wie folgt definiert:
(Φ(g))(σ) =df
(
g(σ{σ(y) ∗ σ(x)/y}{σ(x) − 1/x})
σ
falls σ(x) > 1
sonst
Wir können nun die Approximationen für (fix Φb, S )(σ) bestimmen:
• Φ0 (⊥)(σ) = undefiniert
(
undefiniert
1
• Φ (⊥)(σ) =
σ
falls σ(x) > 1
sonst


 undefiniert
2
• Φ (⊥)(σ) =
σ{σ(x) ∗ σ(y)/y}{σ(x) − 1/x}


σ
falls σ(x) > 2
falls σ(x) = 2
sonst


 undefiniert
n
• Φ (⊥)(σ) =
σ{σ(y) ∗ σ(x) ∗ (σ(x) − 1) ∗ ... ∗ 1/y}{1/x}


σ
Das bedeutet für den Fixpunkt:
(
σ{σ(y) ∗ σ(x) ∗ (σ(x) − 1) ∗ ... ∗ 1/y}{1/x}
(fix Φb, S )(σ) =
σ
Im Falle σ0 (x) = 3 gilt also (fix Φ)(σ0 ) = 1 ∗ 3 ∗ 2 ∗ 1 = 6.
falls σ(x) > n
falls σ(x) ∈ {2, ..., n}
sonst
falls σ(x) > 2
sonst
3
2.3. SEMANTIK VON WHILE
77
Beispiel 2.35
Als zweites Beispiel betrachten wir das Fakultätprogramm:
S =df y = 1; while (1 < x) {y = y ∗ x; x = x − 1}.
Sei zur Abkürzung b =df 1 < x und S 0 =df y = y ∗ x; x = x − 1. Hier gilt für einen
beliebigen Startzustand σ0 :
[[ S ]]ds (σ0 ) = (fix Φb, S )(σ) für σ = σ0 {1/y}
Dann ist Φ =df Φb, S wie folgt definiert:
(
g(σ{(σ(y) ∗ σ(x))/y}{(σ(x) − 1)/x})
(Φ(g))(σ) =df
σ
falls σ(x) > 1
sonst
Wir können nun die Approximationen für (fix Φb, S )(σ) bestimmen:
• Φ0 (⊥)(σ) = undefiniert
(
undefiniert falls σ(x) > 1
1
• Φ (⊥)(σ) =
σ
sonst


falls σ(x) > 2
 undefiniert
2
• Φ (⊥)(σ) =
σ{(σ(x) ∗ σ(y))/y}{(σ(x) − 1)/x} falls σ(x) = 2


σ
sonst


falls σ(x) > n
 undefiniert
n
• Φ (⊥)(σ) =
σ{(σ(y) ∗ σ(x) ∗ (σ(x) − 1) ∗ ... ∗ 1)/y}{1/x} falls σ(x) ∈ {2, ..., n}


σ
sonst
Das bedeutet für den Fixpunkt:
(
σ{(σ(y) ∗ σ(x) ∗ (σ(x) − 1) ∗ ... ∗ 1)/y}{1/x}
(fix Φb, S )(σ) =
σ
Im Falle σ0 (x) = 3 gilt also (fix Φ)(σ0 ) = 1 ∗ 3 ∗ 2 ∗ 1 = 6.
falls σ(x) > 2
sonst
3
Das Koinzidenztheorem
Das folgende Theorem bestätigt die Konsistenz unserer bisherigen Semantikbetrachtungen:
Die operationelle und die denotationelle Semantik stimmen überein.
Theorem 2.36 (Koinzidenztheorem)
∀ S ∈ Stm. [[ S ]]ds = [[ S ]]sos
3
78
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
Beweis
Wegen der Antisymmetrie von “ folgt die Behauptung aus den beiden folgenden Lem”
mata.
2
Lemma 2.37
∀ S ∈ Stm. [[ S ]]sos [[ S ]]ds .
3
Beweis
Offenbar genügt es, für alle Zustände σ, σ 0 die Aussage
(∗)
hS, σi ⇒∗ σ 0 impliziert [[ S ]]ds (σ) = σ 0
zu zeigen. Ein einfaches Induktionsargument (nach der Länge der Berechnungsfolge) reduziert (∗) auf
• hS, σi ⇒ σ 0 impliziert [[ S ]]ds (σ) = σ 0 und
• hS, σi ⇒ hS 0 , σ 0 i impliziert [[ S ]]ds (σ) = [[ S 0 ]]ds (σ 0 ).
Wir beweisen die reduzierte Variante durch Induktion nach der Struktur des Ableitungsbaums für den betrachteten Transitionsschritt.
• Axiome:
Für die Axiome x = a“ und skip“ ist die Sache trivial.
”
”
• [comp1sos ]:
Hier gilt o.B.d.A.hS1 ; S2 , σi ⇒ hS10 ; S2 , σ 0 i wegen hS1 , σi ⇒ hS10 , σ 0 i. Damit kann die
Induktionsvoraussetzung auf hS1 , σi ⇒ hS10 , σ 0 i angewendet werden und man erhält
[[ S1 ]]ds (σ) = [[ S10 ]]ds (σ 0 ). Wegen der Kompositionalität von [[ · ]]ds folgt damit wie
gewünscht:
[[ S2 ]]ds ([[ S1 ]]ds (σ)) = [[ S2 ]]ds ([[ S10 ]]ds (σ 0 )) = [[ S10 ; S2 ]]ds (σ 0 ).
• [comp2sos ]:
Hier gilt hS1 ; S2 , σi ⇒ hS2 , σ 0 i wegen hS1 , σi ⇒ σ 0 . Damit kann die Induktionsvoraussetzung auf hS1 , σi ⇒ σ 0 angewendet werden und man erhält: [[ S1 ]]ds (σ) = σ 0 . Wegen
der Kompositionalität von [[ · ]]ds folgt damit:
[[ S1 ; S2 ]]ds (σ) = [[ S2 ]]ds ([[ S1 ]]ds (σ)) = [[ S2 ]]ds (σ 0 ).
• [iftt
sos ]:
2.3. SEMANTIK VON WHILE
79
Hier gilt [[ b ]]B (σ) = tt und hif (b) {S1 } else {S2 }, σi ⇒ hS1 , σi, und die Behauptung
folgt aus:
[[ if (b) {S1 } else {S2 } ]]ds (σ)
(Definition von [[ · ]]ds ) = cond([[ b ]]B (σ), [[ S1 ]]ds (σ), [[ S2 ]]ds (σ))
([[ b ]]B (σ) = tt)
= [[ S1 ]]ds (σ)
• [ifff
sos ]:
Hier gilt [[ b ]]B (σ) = ff und hif (b) {S1 } else {S2 }, σi ⇒ hS2 , σi, und die Behauptung
folgt aus:
[[ if (b) {S1 } else {S2 } ]]ds (σ)
(Definition von [[ · ]]ds ) = cond([[ b ]]B (σ), [[ S1 ]]ds (σ), [[ S2 ]]ds (σ))
([[ b ]]B (σ) = ff )
= [[ S2 ]]ds (σ)
• [whilesos ]:
Hier gilt
hwhile (b) {S 0 }, σi ⇒ hif (b) {S 0 ; while (b) {S 0 }} else {skip}, σi.
Also gilt nach Definition von [[ · ]]ds :
[[ while (b) {S 0 } ]]ds
= fix Φb, S
0
0
0
(fix Φb, S ist Fixpunkt) = Φb, S (fix Φb, S )
0
0
(Definition von Φb, S )
= cond([[ b ]]B , fix Φb, S ◦ [[ S 0 ]]ds , id)
(Definition von [[ · ]]ds )
= cond([[ b ]]B , [[ S ]]ds ◦ [[ S 0 ]]ds , id)
(Definition von [[ · ]]ds
= cond([[ b ]]B , [[ S 0 ; while (b) {S 0 } ]]ds , id)
(Definition von [[ · ]]ds )
= [[ if (b) {S 0 ; while (b) {S 0 }} else {skip} ]]ds
0
Per Induktionsprinzip folgt die Behauptung.
2
Lemma 2.38
∀ S ∈ Stm. [[ S ]]ds [[ S ]]sos .
3
80
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
Das Lemma kann durch strukturelle Induktion über den syntaktischen Aufbau von S
bewiesen werden. Im Induktionsschluss ist im Fall S = while (b) {S} das Lemma von
Park hilfreich. Dieses besagt, dass der kleinste Fixpunkt einer stetigen Funktion f auf
einer CCPO hD; vi auch der kleinste Präfixpunkt von f ist. Dabei heißt ein Punkt d ∈ D
Präfixpunkt von f , wenn f (d)vd ist (fixf ist natürlich - wie übrigens jeder andere Fixpunkt
von f auch - ein Präfixpunkt von f ).
Lemma 2.39 (Park’s Lemma)
Gegeben sei eine CCPO hD; vi, eine stetige Abbildung f : D → D sowie ein Punkt d ∈ D.
Falls f (d) v d, so ist fixf v d.
3
Die Durchführung der Beweise von Lemma 2.38 und 2.39 bleibt dem Leser als Übungsaufgabe überlassen.
2.3. SEMANTIK VON WHILE
2.3.4
81
Axiomatische Semantik
In diesem Abschnitt wollen wir die Semantik eines WHILE–Programms axiomatisch definieren. Grundlegend dafür sind Aussagen der Form
{P } S {Q},
die als Hoare–Tripel (syntaktische Sicht) oder als Korrektheitsformeln (semantische
Sicht) bezeichnet werden. Dabei sind {P } und {Q} logische Spezifikationen, die Aussagen
über das Programm vor Ausführung von S (Vorbedingung P ) bzw. nach Ausführung
von S (Vorbedingung Q) machen, und S eine Anweisung der Sprache WHILE. Im
folgenden werden wir zunächst die Syntax und Semantik der Formelsprache beschreiben,
die in den Spezifikationen Verwendung findet.
Boolesche Formeln zur Programmspezifikation
Boolesche Formeln (BF) zur Programmspezifikation basieren im wesentlichen auf den
Booleschen Ausdrücken Bexp aus der Sprachbeschreibung von WHILE–Programmen in
Kapitel 2.2. Es gibt jedoch zwei wesentliche Unterschiede:
• Boolesche Formeln besitzen zusätzlich All- und Existenzquantoren, die es ermöglichen über ganzzahlige Variablen zu quantifizieren.
• Wir verwenden statt der an Java angelehnten Booleschen Operatoren und arithmetischen Relationssymbole entsprechende Operatoren, wie sie aus der Mathematik
bekannt sind. So verwenden wir beispielsweise ∨ statt && und = statt == .
Die Erweiterung um Quantoren erhöht die Ausdruckskraft Boolescher Ausdrücke beträchtlich, ist aber notwendig um Programmeigenschaften von WHILE–Programmen genau
genug erfassen zu können. Wir werden später Beispiele kennenlernen, in denen ausgiebig
von quantifizierten Formeln Gebrauch gemacht wird.
Die Konversion Boolescher Ausdrücke von Bexp nach BF ist offensichtlich. Aus diesem Grund werden wir Bexp-Ausdrücke implizit mit BF-Formeln identifizieren. Dadurch
können insbesondere die Bedingungen von WHILE-Programmen auch direkt als Teil von
Programmspezifikationen verwendet werden.
Die BNF für Boolesche Formeln BF ist im folgenden angegeben, wobei a als Metavariable
für arithmetische Ausdrücke aus Kapitel 2.2, b als Metavariable für Boolesche Formeln
und x als Metavariable für ganzzahlige Variablen dient:
b ::= true | f alse | (a = a) | (a < a) | (a ≤ a) | (¬b) | (b ∧ b) | (b ∨ b) |
(∀ x. b) | (∃ x. b)
82
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
Es sei darauf hingewiesen, daß Boolesche Formeln somit sowohl Variablen enthalten können,
die in einem zugrunde liegenden Programm S vorkommen, als auch solche die nicht in S
vorkommen. Derartige Variablen müssen auch nicht an einen Quantor gebunden sein. Oft
werden sie benutzt um in der Vorbedingung “Startwerte” der Programmvariablen festzuhalten, um diese dann in der Nachbedingung zu verwenden.
Die Semantik Boolescher Formeln und die Definitionen für “freie Variablen” kann im
wesentlichen auf die induktiven Definitionen für Boolesche Ausdrücke aus Bexp zurückgeführt werden. Wir verzichten hier auf eine Wiederholung der Definitionen, die sich nur
in den Operatorsymbolen unterscheiden würde. Für quantifizierte Formeln werden die
induktiven Definitionen allerdings wie folgt ergänzt.
• Die Semantikfunktion [[ · ]]B : BF → (Σ → B) wird induktiv erweitert durch:
[[ ∀ x. b ]]B (σ)
=df
tt gdw. [[ b ]]B (σ{z/x}) = tt für alle z ∈ Z
[[ ∃ x. b ]]B (σ)
=df
tt gdw. [[ b ]]B (σ{z/x}) = tt für ein z ∈ Z
• Die Menge der freien Variablen FV : Aexp ∪ BF → P(Var) wird induktiv erweitert
durch:
FV(∀ x. b)
=df
FV(b) \ {x}
FV(∃ x. b)
=df
FV(b) \ {x}
Das heißt, in einer quantifizierten Formel (∀ x. b) oder (∀ x. b) werden alle freien
Vorkommen von x in b an den Quantor gebunden und sind damit nicht mehr frei.
Auch die syntaktische Substitution wird auf Boolesche Formeln erweitert. Dieses ist bis
auf die quantifizierten Formeln vollkommen analog zu der induktiven Definition f ür arithmetische Ausdrücke. Im Falle quantifizierter Formeln muss allerdings vor der Substitution
im Rumpf der Formel die gebundene Variable ggf. umbenannt werden. Denn es ist zu
vermeiden, daß bei der Substitution Vorkommen dieser Variablen, die bislang frei im einzukopierenden Ausdruck sind, fälschlicherweise an den Quantor gebunden werden. Die
syntaktische Substitution wie unten spezifiziert ist daher nur bis auf die Umbenennung
von gebundenen Variablen eindeutig. Wir verwenden dennoch eine Funktionsnotation
·[·/·] : BF × Aexp × Var → BF, was dadurch zu rechtfertigen ist, daß die Umbenennung fest, aber beliebig vorgenommen werden kann. Es sei außerdem angemerkt, daß in
der späteren Anwendung kein Bedarf an Umbennungen entsteht, wenn Variablen hinter
Quantoren stets so gewählt werden, daß diese nicht in dem zugrundeliegenden Programm
vorkommen.
2.3. SEMANTIK VON WHILE
true[a/x]
=df
true
f alse[a/x]
=df
f alse
(a1 = a2 )[a/x]
=df
(a1 [a/x] = a2 [a/x])
(a1 < a2 )[a/x]
=df
(a1 [a/x] < a2 [a/x])
(a1 ≤ a2 )[a/x]
=df
(a1 [a/x] ≤ a2 [a/x])
(¬b)[a/x]
=df
(¬b[a/x])
(b1 ∧ b2 )[a/x]
=df
(b1 [a/x] ∧ b2 [a/x])
(b1 ∨ b2 )[a/x]
=df
(b1 [a/x] ∨ b2 [a/x])
(∀ x. b)[a/y]
=df
(∀ z. b[z/x][a/y]) wobei z ∈
/ FV(a) ∪ FV(b) ∪ {y}
(∃ x. b)[a/y]
=df
(∃ z. b[z/x][a/y]) wobei z ∈
/ FV(a) ∪ FV(b) ∪ {y}
83
Wie bislang gewohnt, verzichten wir auch in Booleschen Formeln auf unnötige Klammersymbole und verwenden zusätzliche Abkürzungen. Dabei folgen wir den Konventionen aus
1.32 und 1.33. Die Substitutionsoperation hat wie bisher die höchste Präzedenz, gefolgt
von den Relationsoperatoren, gefolgt von der Negation, gefolgt vom Konjunktionsoperator, gefolgt vom Disjunktionsoperator. Der All- und Existenzquantor hat schließlich die
niedrigste Präzedenz unter allen Operatoren.
Partielle und totale Korrektheit
Im folgenden werden wir zwei Varianten der Definition einer Semantik für Korrektheitsformeln {P } S {Q} vorstellen, die zum einen die partielle Korrektheit und zum anderen
die totale Korrektheit respektieren. Dabei bezeichnet [[ S ]] = [[ S ]] sos = [[ S ]]ds die operationelle bzw. die denotationelle Semantik von S (vgl. Theorem 2.36).
Zunächst benötigen wir jedoch den Begriff der Charakterisierung eines Booleschen Ausdrucks.
Definition 2.40 (Charakterisierung)
Sei P eine Boolesche Formel. Dann heißt
Ch(P ) =df {σ ∈ Σ | [[ P ]]B (σ) = tt}
die Charakterisierung von P .
3
84
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
In den folgenden Abschnitten werden wir in Beweisen mehrere Eigenschaften der Charakterisierung einer Booleschen Formel ausnutzen, welche unmittelbar aus der Definition der
Charakterisierung folgen.
Lemma 2.41 (Eigenschaften von Ch(·))
Seien P, Q Boolesche Formeln. Dann gilt:
1. Ch(P ∧ Q) = Ch(P ) ∩ Ch(Q)
2. Ch(P ∨ Q) = Ch(P ) ∪ Ch(Q)
3. Ch(¬P ) = Σ \ Ch(P )
3
Bezeichnen wir nun die Menge aller Zustände, für die [[ S ]] definiert ist, mit Def ([[ S ]]), so
können wir die Semantik von Korrektheitsformeln wie folgt definieren:
Definition 2.42 (Semantik von Korrektheitsformeln)
Eine Korrektheitsformel {P } S {Q} heißt
• partiell korrekt, falls [[ S ]](Ch(P )) ⊆ Ch(Q).
Dabei sei die Semantikfunktion erweitert auf Zustandsmengen Φ ⊆ Σ durch:
[[ S ]](Φ) =df {[[ S ]](σ) | σ ∈ (Φ ∩ Def ([[ S ]]))}.
• total korrekt, falls {P } S {Q} partiell korrekt ist und Def ([[ S ]]) ⊇ Ch(P ) gilt.
3
Intuitiv ist eine Korrektheitsformel {P } S {Q} partiell korrekt, falls jeder terminierende Programmlauf, der in einem Zustand startet, der P erfüllt, in einem Zustand terminiert,
der Q erfüllt. Sie ist total korrekt, falls jeder Programmlauf, der in einem Zustand startet,
welcher P erfüllt, terminiert, und zwar in einem Zustand, der Q erfüllt. Der Unterschied
zwischen den beiden Erfüllbarkeitskonzepten besteht also darin, daß im Falle der totalen
Korrektheit die Korrektheitsformel auch die Termination impliziert, wohingegen im Falle der partiellen Korrektheit über divergierende (nicht terminierende) Programme nichts
ausgesagt wird.
Wollen wir eine Korrektheitsformel nur bzgl. partieller (totaler) Korrektheit betrachten,
so sprechen wir von einer partiellen (totalen) Korrektheitsformel. Wir werden uns
im folgenden vorwiegend mit der partiellen Korrektheit beschäftigen.
2.3. SEMANTIK VON WHILE
85
Beispiel 2.43
• {x = x0 } x = x2 {x = x20 } ist partiell und total korrekt.
• {true} x = 0 {x ≤ 7} ist partiell und total korrekt.
• {false} x = 35 {x = 0}
Da false durch die leere Menge charakterisiert ist, ist diese Korrektheitsformel trivialerweise partiell und total korrekt.
• {true} S {false} mit einem beliebigen WHILE–Programm S:
Diese Korrektheitsformel beschreibt den Fall, daß aus jedem beliebigen Startzustand
kein Endzustand erreicht wird. Dies ist nur dann möglich, wenn das Programm
S niemals terminiert (immer divergiert), wie z.B. while (true) {skip}. Die Korrektheitsformel ist für ein immer divergierendes Programm partiell aber nicht total
korrekt, für ein Programm, das für mindestens eine Eingabe terminiert, aber nicht
partiell und nicht total korrekt.
3
Der folgenden Abschnitt stellt eine streng formale Beweismethode für partielle Korrektheitsformeln vor: den Hoare–Kalkül. Dabei bedeutet ‘streng formal’ regelbasiert. Zum
Vergleich: Auch die strukturierte operationelle Semantik wurde regelbasiert angegeben.
Eine solche streng formale, syntax–basierte Beschreibung eignet sich besonders gut f ür
eine maschinelle Realisierung: bei der strukturierten operationellen Semantik zur Implementierung eines Interpreters und bei dem im folgenden vorgestellten Hoare–Kalk ül zur
(halb-)automatischen Verifikation von Programmen. Anschließend wird die Korrektheit
und die Vollständigkeit des Hoare–Kalküls relativ zur operationellen (bzw. denotationellen) Semantik untersucht.
Der Hoare–Kalkül
Ähnlich zum Regelsystem zur strukturierten operationellen Semantik besteht der Hoare–
Kalkül aus Regeln der Form
Name
Prämisse
.
Konklusion
Dabei ist die Prämisse im wesentlichen aus Korrektheitsformeln (keine, eine oder zwei)
zusammengesetzt, wohingegen die Konklusion aus genau einer Korrektheitsformel besteht. Regeln ohne Prämisse heißen auch hier Axiome.
86
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
Wir wollen nun die einzelnen Regeln dieses Regelsystems vorstellen. Der Hauptteil des
Regelsystems ist syntaxtorientiert: jedem WHILE-Konstruktor ist (genau) eine Regel
zugeordnet.
• skip–Regel:
SKIP
−
{P } skip {P }
Die Semantik von skip ist die identische Abbildung auf dem Speicherzustand.
• Assignment–Regel:
ASS
−
{Q[t/x]} x = t {Q}
Die Assignment–Regel ist ebenfalls ein Axiom. Dabei ist die Substitution [t/x] ( t
”
für x“) eine rein syntaktische Operation. Q[t/x] geht folglich aus der Formel Q durch
die syntaktische Ersetzung von x durch t hervor.
• Kompositionsregel:
KOM P
{P } S1 {P 0 } {P 0 } S2 {Q}
{P } S1 ; S2 {Q}
Diese Regel beschreibt, wie sich die partielle Korrektheit von Korrektheitsformeln
für S1 und S2 auf Korrektheitsformeln für die sequentiellen Komposition S 1 ; S2 weitervererbt.
• if-then-else–Regel:
IT E
{P ∧ b} S1 {Q} {P ∧ ¬b} S2 {Q}
{P } if (b) {S1 } else {S2 } {Q}
Bei dieser Regel ist beachtenswert, wie sich der Effekt der bedingten Verzweigung
durch die beiden Korrektheitsformeln in der Prämisse widerspiegelt.
• while–Regel:
W HILE
{I ∧ b} S {I}
{I} while (b) {S} {I ∧ ¬b}
Dabei repräsentiert die Formel I eine sog. Schleifeninvariante:
Eine Invariante ist eine Formel, deren Gültigkeit in jeder Schleifeniteration erhalten bleibt.
Invarianten erlauben es, Aussagen über Schleifen durch Induktion zu beweisen. Die
Invariante selbst steht für die zu beweisende Behauptung. Induktionsanfang ist der
Beweis der Gültigkeit der Invariante vor Betreten der Schleife, und der Induktionsschritt ist der Beweis der Invarianz unter den Schleifeniterationen.
Die Prämisse der while–Regel formuliert die Invarianteneigenschaft: Gilt I und ist b
erfüllt, d.h. die Schleife wird (noch) einmal durchlaufen, so erhält der Schleifenrumpf
2.3. SEMANTIK VON WHILE
87
S die Gültigkeit der Formel I.
Die Konklusion der while–Regel besagt: Gilt vor Ausführung der Schleife die Invariante I ( Induktionsanfang“), so gilt nach dem Verlassen der Schleife (falls diese
”
terminiert) ebenfalls I, und die Abbruchbedingung ¬b ist erfüllt.
Beachte: Es gibt zwar immer die trivialen Invarianten true und false, ein sinnvoller
Beweis benötigt aber in der Regel andere, komplexere Invarianten. Diese zu ermitteln
ist eine der Hauptschwierigkeiten im Umgang mit dem Hoare–Kalkül.
Außer diesen rein syntax-orientierten Regel benötigen wir noch eine weitere Regel zur
Adaption der Vor– und Nachbedingungen.
• Konsequenzregel:
KON S
P ⇒P0
{P 0 } S {Q0 }
{P } S {Q}
Q0 ⇒ Q
Dabei steht ⇒“ für die logische Implikation. Die Konsequenzregel besagt, daß die
”
partielle Korrektheitsformel {P } S {Q} gültig ist, wenn die Vorbedingung P die
Formel P 0 impliziert, {P 0 } S {Q0 } partiell korrekt ist und Q0 wiederum die Nachbedingung Q impliziert. Oder intuitiv: Korrektheitsformeln (hier {P 0 } S {Q0 }) dürfen
abgeschwächt werden, indem man die Vorbedingung verschärft und die Nachbedingung abschwächt.
Bemerkung 2.44
Wegen der Form der Konklusion wird die Assignment-Regel auch R ückwärts–Assignment–Regel genannt: Aus der Nachbedingung Q einer Zuweisung x = t läßt sich mit
Hilfe der Assignment–Regel eine gültige Vorbedingung Q[t/x] konstruieren. Tatsächlich
gibt es zu dieser Regel auch eine äquivalente Vorwärts–Variante:
ASSv
−
{P } x = t {∃ n. (P [n/x] ∧ t[n/x] = x)}
deren Korrektheit sich mit Hilfe der Rückwärts–Assignment–Regel beweisen läßt. Um diese
Regel besser begreifbar zu machen, wollen wir kurz erläutern, warum die Vorwärtsvariante
nicht einfach wie folgt aussehen kann:
−
{P } x = t {P [t/x]}
Diese Regel würde beispielsweise die partielle Korrektheit der Korrektheitsformel
{x = 3} x = 5 {5 = 3}
rechtfertigen, was offensichtlich falsch wäre.
3
Die Bedeutung des Hoare–Regelsystems besteht darin, daß es erlaubt, formale Beweise f ür
die partielle Korrektheit einer Korrektheitsformel {P } S {Q} zu führen. Das funktioniert
wie folgt:
88
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
• Versuche die aktuell betrachtete Korrektheitsformel mit Hilfe der Konsequenzregel
so zu modifizieren, daß die dem führenden Programmkonstruktor zugeordnete Regel
anwendbar wird. Dieser Schritt kann in Sonderfällen überflüssig sein.
• Wende die dem führenden Programmkonstruktor zugeordnete Regel an. Ist das nicht
möglich, d.h. kann die Korrektheitsformel nicht mit Hilfe der Konsequenzregel geeignet adaptiert werden, so scheitert der Beweis.
Bis auf die drei folgenden Punkte ist dieses Vorgehen schematisch:
• Die Auswahl der geeigneten adaptierenden Abschwächungsschritte und der Beweis
der Gültigkeit der verwendeten Implikationen.
• Das Finden der Invarianten.
• Die Auswahl der als Verbindungsglied dienenden Booleschen Formel bei der Anwendung der Kompositionsregel.
Während die ersten beiden Punkte die tatsächlichen Schwierigkeiten im Umgang mit dem
Hoare–Kalkül charakterisieren, erfordert der dritte Punkt nur eine systematische Beweiskonstruktion: Die Nachbedingung muß sukzessive entgegen dem üblichen Kontrollfluß solange umgewandelt werden, bis das Programm rückwärts voll abgearbeitet ist.
Die Notwendigkeit der Konsequenzregel wird anhand der folgenden, offensichtlich partiell
korrekten Korrektheitsformel deutlich:
{x = 3} x = 5 {x = 5}.
Ein formaler Beweis ohne Konsequenzregel ist zum Scheitern verurteilt, da nur die Assignment–Regel mit dem Resultat
{5 = 5} x = 5 {x = 5}
angewendet werden kann. Danach stagniert der Beweisversuch, da die Kluft zwischen
den Vorbedingungen {x = 3} und {5 = 5} ohne Konsequenzregel nicht überbrückt werden
kann. Mit Hilfe der Konsequenzregel kann der Beweis aber erfolgreich fortgesetzt werden:
Da 5 = 5 logisch äquivalent zu true ist, gilt (x = 3) ⇒ (5 = 5). Also kann die Konsequenzregel wie folgt eingesetzt werden:
KON S
(x = 3) ⇒ (5 = 5),
{5 = 5} x = 5 {x = 5},
{x = 3} x = 5 {x = 5}
(x = 5) ⇒ (x = 5)
Zusammen mit der bereits etablierten partiellen Korrektheit von {5 = 5} x = 5 {x = 5}
vervollständigt das den formalen Beweis.
2.3. SEMANTIK VON WHILE
CONS
89
ASS
I’’, {I’’} y=y*m {I’}
(I β)
COMP
{(I
WHILE
CONS
ASS
{I’} x=x-1 {I}
β)} y=y*m {I’},
{(I β)} y=y*m; x=x-1 {I}
I, {I} S {(I ( β)) }, (I
{pre} S {post}
pre
( β))
post
Abbildung 2.6: Beweisbaum für die partielle Korrektheit der Exponentiation
Beispiel 2.45
Formale “Hoare-Beweise” notiert man am besten in Form eines Beweisbaumes. Folgendes Programm Sexp
Sexp
=df
z = y; while (!(x == 1)) {y = z ∗ y; x = x − 1} .
{z
}
|
SW
berechnet für positive Argumente x das Ergebnis der Exponentiation y x . Formal erfülltSexp
also die folgende Anforderung an die partielle Korrektheit:
{x = n ∧ y = m ∧ n > 0} Sexp {y = mn }
{z
}
| {z }
|
pre
post
Der zu einem formalen Hoare-Beweis partieller Korrektheit gehörige Beweisbaum ist in
Abbildung 2.6 dargestellt.
Zentral ist hierbei die Verwendung der Schleifeninvarianten
I =df (y ∗ z x−1 = mn ∧ x > 0).
Im Beweisbaum werden zur Vereinfachung der Notation folgende folgende Abkürzungen
verwendet:
b
=df
¬(x = 1)
Iz
=df
I[y/z]
Ix
=df
I[x − 1/x]
Ixy
=df
Ix [y ∗ m/y]
90
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
Die in den Konsequenzregeln auftretenden Implikationen sind im Deatail begründet wegen:
pre
=
(x = n ∧ y = m ∧ n > 0)
⇒ (y x = mn ∧ x > 0)
⇒ (y ∗ y x−1 = mn ∧ x > 0)
=
(I ∧ ¬b)
=
Iz
(y ∗ z x−1 = mn ∧ x > 0 ∧ ¬(¬(x = 1)))
⇒ (y ∗ z x−1 = mn ∧ x = 1)
⇒ (y ∗ z 0 = mn )
⇒ (y = mn )
(I ∧ b)
=
post
=
(y ∗ z x−1 = mn ∧ x > 0 ∧ ¬(x = 1))
⇒ (y ∗ z x−1 = mn ∧ x > 1)
⇒ (y ∗ z ∗ z x−2 = mn ∧ x − 1 > 0)
=
((y ∗ z) ∗ z (x−1)−1 = mn ∧ x − 1 > 0)
=
Ixy
3
Korrektheit der Methode von Hoare
In diesem Abschnitt wollen wir die Korrektheit der Methode von Hoare beweisen. Zunächst betrachten wir den Fall der partiellen Korrektheit. Anschließend wird eine Modifikation zur Erfassung der totalen Korrektheit vorgestellt.
Satz 2.46 (Korrektheit des Hoare-Kalk üls für partielle Korrektheit)
Mit Hilfe des Hoare–Kalküls lassen sich nur partiell korrekte Korrektheitsformeln ableiten.
3
Beweis
Wir beweisen den Satz durch Induktion nach der Anzahl n der verwendeten Beweisschritte
(im Beweisbaum).
2.3. SEMANTIK VON WHILE
91
Induktionsanfang: (n = 1)
Im ersten (und einzigen) Schritt kann nur ein Axiom angewendet worden sein. Die Korrektheit für den Fall n = 1 folgt also aus der Korrektheit der Skip– und der Assignmentregel.
Dazu:
1. skip–Regel:
Die hier zu verifizierende Korrektheitsformel {P } skip {P } ist offensichtlich partiell
korrekt.
2. Assignment–Regel:
Hier ist die partielle Korrektheit von {Q[t/x]} x = t {Q} nachzuweisen. Sei dazu
σ ∈ Ch(Q[t/x]). Dann genügt es σ{[[ t ]]A (σ)/x} ∈ Ch(Q) zu zeigen (vgl. dies auch
mit der operationellen bzw. denotationellen Semantik).
Dieser Zusammenhang (es gilt sogar σ ∈ Ch(Q[t/x]) ⇐⇒ σ{[[ t ]] A (σ)/x} ∈ Ch(Q))
wird üblicherweise als Substitutionslemma bezeichnet (vgl. Kapitel 1, Lemma
1.43 und Kapitel 2, Lemma 2.6). Der zugehörige formale Beweis erfolgt wie üblich
mittels struktureller Induktion nach t. Zentral ist dabei wieder die strukturelle Definition von [./.]:


 Q
Q[t/x] =
t


op(Q1 [t/x], ..., Qn [t/x])
falls Q Konstante oder Variable 6= x
falls Q = x
falls Q = op(Q1 , ..., Qn )
Siehe Folien zur Vorlesung (Teil 1): Seite 195-198
Für den Induktionsschluß können wir nun nach Induktionsvoraussetzung annehmen, daß
alle in höchstens n Schritten abgeleiteten Korrektheitsformeln partiell korrekt sind.
Induktionsschluß: (n → n + 1)
Hier muß gezeigt werden, daß der n + 1-te Beweisschritt die partielle Korrektheit erhält.
Der Beweis erfordert eine Fallunterscheidung bzgl. der dabei angewendeten Regel:
1. Kompositionsregel:
D.h. in der folgenden Situation
KOM P
{P } S1 {P 0 } {P 0 } S2 {Q}
{P } S1 ; S2 {Q}
muß unter der (Induktions–) Voraussetzung
• [[ S1 ]](Ch(P )) ⊆ Ch(P 0 )
• [[ S2 ]](Ch(P 0 )) ⊆ Ch(Q).
92
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
gezeigt werden, daß
[[ S1 ; S2 ]](Ch(P )) ⊆ Ch(Q)
gilt. Dies folgt aber unmittelbar aus
[[ S1 ; S2 ]] = [[ S2 ]] ◦ [[ S1 ]].
2. if-then-else–Regel:
D.h. in der Situation
IT E
{P ∧ b} S1 {Q} {P ∧ ¬b} S2 {Q}
{P } if (b) {S1 } else {S2 } {Q}
muß unter der (Induktions–) Voraussetzung
• [[ S1 ]](Ch(P ∧ b)) ⊆ Ch(Q)
• [[ S2 ]](Ch(P ∧ ¬b)) ⊆ Ch(Q)
gezeigt werden, daß
[[ if (b) {S1 } else {S2 } ]](Ch(P )) ⊆ Ch(Q)
gilt. Zum Beweis nutzen wir
[[ if (b) {S1 } else {S2 } ]](σ) =df
(
[[ S1 ]](σ)
[[ S2 ]](σ)
falls [[ b ]]B (σ) = tt
falls [[ ¬b ]]B (σ) = tt
Sei nun σ ∈ Ch(P ) beliebig und o.B.d.A. [[ b ]] B (σ) = tt (der Fall [[ b ]]B (σ) = ff geht
analog). Dann gilt σ ∈ Ch(P ∧ b), also nach Induktionsvoraussetzung [[ S 1 ]]({σ}) ⊆
Ch(Q). Andererseits gilt in diesem Falle unmittelbar nach der Definition der Semantik [[ S1 ]](σ) = [[ if (b) {S1 } else {S2 } ]](σ), und damit folgt die Behauptung.
3. While–Regel:
D.h. in der Situation
W HILE
{I ∧ b} S {I}
,
{I} while (b) {S} {I ∧ ¬b}
muß unter der (Induktions–) Voraussetzung
[[ S ]](Ch(I ∧ b)) ⊆ Ch(I)
gezeigt werden, daß
[[ while (b) {S} ]](Ch(I)) ⊆ Ch(I ∧ ¬b)
gilt. Punktweise, also für jeden Zustand σ ∈ Ch(I), heißt das:
[[ while (b) {S} ]]({σ}) ⊆ Ch(I ∧ ¬b)
Im Falle der Divergenz der While–Schleife für σ ist die Behauptung trivial, da
[[ while (b) {S} ]]({σ}) = ∅. Ansonsten beweisen wir sie durch vollständige Induktion
über die Anzahl der Iterationen k der While–Schleife.
Induktionsanfang (k = 0):
2.3. SEMANTIK VON WHILE
93
In diesem Fall ist vor Betreten der While-Schleife [[ b ]] B (σ) = ff und deswegen [[ while (b) {S} ]](σ) = id. Die Behauptung folgt also direkt aus
Lemma 2.41.
Induktionsschluß (k → k+1):
Sei σ ∈ Ch(I ∧ b) beliebig, und die While-Schleife werde (k+1)-mal durchlaufen. Nach dem ersten Schleifendurchlauf gilt nach Induktionsvoraussetzung
σ 0 =df [[ S ]](σ) ∈ Ch(I)
und die Schleife wird noch weitere k-mal durchlaufen. Dann gilt aufgrund
der Induktionsvoraussetzung [[ while (b) {S} ]](σ 0 ) ∈ Ch(I ∧ ¬b), also insgesamt wie gewünscht:
[[ while (b) {S} ]](σ) = [[ S; while (b) {S} ]](σ)
= [[ while (b) {S} ]]([[ S ]](σ))
= [[ while (b) {S} ]](σ 0 ) ∈ Ch(I ∧ ¬b).
Per Induktionsprinzip folgt die Behauptung.
4. Konsequenzregel:
D.h. in der Situation
KON S
P ⇒P0
{P 0 } S {Q0 }
{P } S {Q}
Q0 ⇒ Q
muß unter der (Induktions–) Voraussetzung
(∗)
[[ S ]](Ch(P 0 )) ⊆ Ch(Q0 )
gezeigt werden, daß
[[ S ]](Ch(P )) ⊆ Ch(Q)
gilt.
Wegen P ⇒ P 0 und Q0 ⇒ Q gelten jeweils die Zustandsinklusionen
(i) Ch(P ) ⊆ Ch(P 0 )
und
0
(ii)
Ch(Q ) ⊆ Ch(Q).
Dann ergibt sich offensichtlich:
(i)
(∗)
(ii)
[[ S ]](Ch(P )) ⊆ [[ S ]](Ch(P 0 )) ⊆ Ch(Q0 ) ⊆ Ch(Q).
Per Induktionsprinzip folgt die Behauptung.
2
Bei genauer Betrachtung unseres bisherigen Vorgehens fällt auf, daß z.B. auch {P } S {Q}
94
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
KONS
(I
b
ASS
Ixy+ , { I +xy } y = z*y { I x+ }
x 0 = x)
KOMP
{ (I
b
+
x 0 = x) } y = z*y { I x }
ASS
{ I x+ } x = x-1 {I + }
+
+
b x 0 = x) } y = z*y; x = x-1 {I }, I
{ I } S W { ( I ( b))}
{ Iz } z = y { I }
KOMP
post
pre
I z , { I z } Sexp {( I ( b))} ,( I ( b))
KONS
post
{
}
{ pre} S exp
WHILE
ASS
{ (I
(x > 0)
Abbildung 2.7: Beweisbaum für die totale Korrektheit der Exponentiation
für S ≡ while (true) {skip} und beliebige Spezifikationen P und Q partiell korrekt ist. S
ist also so etwas wie ein universelles Programm. Tatsächlich erfüllt es sogar {true} S {false}.
Diese theoretisch hübsche Eigenschaft ist leider praxisfern, da einem Anwender mit diesem
Programm wenig gedient ist. Daher ist in der Praxis entweder eine separate Terminationsbetrachtung oder ein integrierter Beweis der totalen Korrektheit von Nöten.
Der integrierte Beweis der totalen Korrektheit kann mit Hilfe folgender modifizierter
While–Regel geführt werden:
W HILEtot
{I ∧ b ∧ (z = t)} S {I ∧ (t < z)}, I ⇒ (t ≥ 0)
{I} while (b) {S} {I ∧ ¬b}
wobei t ein Ausdruck ist, der von den Variablen, die im Programm S vorkommen, abhängt,
und z eine “frische” freie Variable ist, also eine die nicht in I, b, t oder S vorkommt. Die
Regel WHILEtot entspricht der alten While–Regel, wobei die Beweisverpflichtung in der
Prämisse um eine Terminierungsanforderung erweitert ist. Der Ausdruck t stellt dabei
die Terminierungsformel dar. Nach einmaliger Ausführung des Schleifenrumpfes muss der
t-Wert echt abnehmen. Die “frische” Variable z stellt sicher, dass der “alte” Wert in der
Nachbedingung noch zur Verfügung steht. Die hinzugekommene Implikation I ⇒ (t ≥ 0)
in der Prämisse gewährleistet schließlich, dass der t-Wert nie negativ wird.
Beispiel 2.47
Betrachten wir erneut das Exponentiationsprogramm aus Beispiel 2.45 so können wir den
Beweis leicht zu einem Beweis für die totale Korrektheit von
{x = n ∧ y = m ∧ n > 0} Sexp {y = mn }
|
{z
}
| {z }
pre tot
post
erweitern. Der formale Beweis ist in Abbildung 2.7 dargestellt.
Die Schleifeninvariante I wird direkt aus dem Beweis partieller Korrektheit übernommen.
Die Termierungsformel t ist hier denkbar einfach, nämlich direkt durch die Variable x
gegeben.
2.3. SEMANTIK VON WHILE
95
Zusätzlich zu den Abkürzungen aus Beispiel 2.45 verwenden wir folgende Notationen:
I+
=df
(I ∧ x < x0 )
Ix+
=df
(I + [x − 1/x])
+
Ixy
=df
Ix+ [y ∗ z/y]
3
Die Implikation I ⇒ (x ≥ 0) in der Prämisse der WHILEtot -Regel ist trivial. Unter den
drei Implikationen der Konsequenzregeln sind 2 bereits in Beispiel 2.45 gezeigt worden, so
verbleibt einzig der Nachweis einer Implikation:
(I ∧ b ∧ x0 = x) ⇒ . . . Wie in Bspl. 2.45 . . .
⇒ (Ixy ∧ x0 = x)
⇒ (Ixy ∧ x − 1 < x0 )
=
+
Ixy
Nicht immer ist die Terminierungsformel so einfach wie im vorangegangenen Beispiel. Wir
betrachten daher im folgenden ein weiteres Beispiel, wo eine nichttriviale Terminierungsformel Verwendung findet.
Beispiel 2.48
Das Programm
Sggt
=df
while (!(x == y)) {if (x < y) {y = y − x} else {x = x − y}}
berechnet den größten gemeinsamen Teiler von x und y. Daher kann die totale Korrektheit
von
{x = n ∧ y = m ∧ n > 0 ∧ m > 0} Sggt {IsGgT (x, n, m)}
gezeigt werden. Dabei drückt für Variablen a, b, c der Ausdruck IsGgT (a, b, c) aus, dass a
der ggT von b und c ist. Formal steht der Ausdruck als Abkürzung für folgenden Booleschen
Ausdruck der Formelsprache:
IsGgT (a, b, c) =df a | b ∧ a | c ∧ (∀ d. d | b ∧ d | c ⇒ d ≤ a)
wobei für Variablen a, b der Ausdruck a | b ausdückt, dass a Teiler von b ist. Auch dieser
Ausdruck kann wieder in der der Formelsprache spezifiziert werden:
a | b =df ∃ c. a ∗ c = b.
3
96
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
ASS
ASS
Iy ,{Iy }y = y-x {I} Ie Ix ,{Ix}x = x-y {I} CONS
CONS
{It } y = y-x {I}, {Ie } x = x-y {I}
ITE
pre
{Iif } if (..){...} else {...} {I}
WHILE
post
post
pre
post
I, {I} while (..){...} {Iw } ,I w
CONS
{pre} while (..){...} {post}
It
Abbildung 2.8: Beweisbaum für die partielle Korrektheit des ggT-Programmes
Es sei hier darauf hingewiesen, dass die so spezifizierte ggT-Eigenschaft in hohem Masse
von Quantoren Gebrauch macht und sogar die Schachtelung von all- und existenzquantifizierten Aussagen verwendet.
Der formale Hoare-Beweis benutzt in den Konsequenzregeln folgendes Lemma als zahlentheoretische Grundlage der in den Konsequenzregeln verwendeten Implikationen:
Lemma 2.49 (IsGgT -Lemma)
1. (x > 0 ∧ x = y ∧ IsGgT (z, x, y)) ⇒ z = x
2. (y > 0 ∧ x > y ∧ IsGgT (z, x, y)) ⇒ IsGgT (z, x − y, y)
3. (x > 0 ∧ y > x ∧ IsGgT (z, x, y)) ⇒ IsGgT (z, y, y − x)
3
Der Beweis ist eine leichte Übungsaufgabe.
Zentral für den eigentlichen Hoare-Beweis ist folgende Invariante, die informell ausdr ückt,
dass die Modifikation von x und y keine Auswirkungen auf den zu berechnenden ggT
haben.
I =df (x > 0 ∧ y > 0 ∧ (∃ z. IsGgT (z, x, y) ∧ IsGgT (z, n, m)))
Der zum formalen Hoare-Beweis gehörige Beweisbaum ist in Abbildung 2.8 dargestellt.
Dabei werden folgende Bezeichnungen verwendet:
2.3. SEMANTIK VON WHILE
97
pre
=df
(x = n ∧ y = n ∧ n > 0 ∧ n > 0)
post
=df
IsGgT (x, n, m)
I
=df
(x > 0 ∧ y > 0 ∧ (∃ z. IsGgT (z, x, y) ∧ IsGgT (z, n, m)))
Iwpost
=df
(¬(¬(x = y)) ∧ I)
Iifpre
=df
(¬(x = y) ∧ I)
It
=df
(x < y ∧ Iifpre )
Ie
=df
(¬(x < y) ∧ Iifpre )
Ix
=df
I[x − y/x]
Iy
=df
I[y − x/y]
Es bleibt die Rechtfertigung der Implikationen in den Konsequenzregeln. Hier untersuchen
wir alle vier relevanten Implikationen:
pre ⇒ I . Es gilt:
pre
≡
(x = n ∧ y = n ∧ n > 0 ∧ n > 0)
[Def. x = n ∧ y = m] ⇒ (x > 0 ∧ y > 0 ∧ (∃ z.IsGgT (z, x, y) ∧ IsGgT (z, n, m)))
[Def. I]
≡
I
Iwpost ⇒ post . Es gilt:
Iwpost
=
(¬(¬(x = y)) ∧ I)
[Einsetzen von I]
=
(¬(¬(x = y)) ∧ x > 0 ∧ y > 0 ∧ (∃ z. IsGgT (z, x, y) ∧ IsGgT (z, n, m)))
⇒ (x = y ∧ x > 0 ∧ y > 0 ∧ (∃ z. IsGgT (z, x, y) ∧ IsGgT (z, n, m)))
[Einsetzen x für y] ⇒ (x > 0 ∧ y > 0 ∧ (∃ z. IsGgT (z, x, x) ∧ IsGgT (z, n, m)))
[Lemma 2.49(1)] ⇒ (∃ z. (z = x) ∧ IsGgT (z, n, m)))
⇒ IsGgT (x, n, m)
[Def. post]
=
post
98
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
Ie ⇒ Ix . Es gilt:
Ie
=
(¬(x < y) ∧ Iifpre )
[Einsetzen von Iifpre ]
=
(¬(x < y) ∧ ¬(x = y) ∧ I)
⇒ x>y∧I
[Einsetzen I]
=
(x > y ∧ x > 0 ∧ y > 0 ∧ (∃ z. IsGgT (z, x, y) ∧ IsGgT (z, n, m)))
[Lemma 2.49(2)] ⇒ (x − y > 0 ∧ y > 0 ∧ (∃ z. IsGgT (z, x − y, y) ∧ IsGgT (z, n, m)))
=
I[x − y/x]
[Def. Ix ]
=
Ix
It
=
(x < y ∧ Iifpre )
[Einsetzen von Iifpre ]
=
(x < y ∧ ¬(x = y) ∧ I)
It ⇒ Iy . Es gilt:
⇒ (x < y ∧ I)
[Einsetzen I]
=
(x < y ∧ x > 0 ∧ y > 0 ∧ (∃ z. IsGgT (z, x, y) ∧ IsGgT (z, n, m)))
[Lemma 2.49(3)] ⇒ (x > 0 ∧ y − x > 0 ∧ (∃ z. IsGgT (z, y, y − x) ∧ IsGgT (z, n, m)))
[Def. Iy ]
=
I[y − x/y]
=
Iy
Will man diesen Beweis zu einem Beweis für totale Korrektheit erweitern, muss eine geeignete Terminierungsformel sowohl von x als auch von y abhängen, da in den Schleifeniterationen nur einer der Werte modifiziert wird. Sowohl max(x, y) als auch (x + y)
sind hier geeignete Terminierungsformeln, denn der Wert dieser Ausdrücke nimmt in jeder
Schleifeniteration echt ab. Wir verzichten an dieser Stelle auf einen formalen Beweis, und
überlassen diesen dem Leser als Aufgabe.
Als eine zweite Variante einer W HILE-Regel für totale Korrektheit findet man in der
Literatur auch:
W HILEtot
(P (n) ∧ n > 0) ⇒ b, {P (n + 1)} S {P (n)}, P (0) ⇒ ¬b
{∃ n. P (n)} while (b) {S} {P (0)}
Dabei bezeichnet P (n) ein Prädikat, das von einer freien Variablen n abhängt, die nicht
in S vorkommt. Somit ist insbesondere die Prämisse implizit allquantifiziert (∀ n ∈ N.
Prämisse). P (n) setzt sich aus der Invarianten I der While–Schleife und aus einer “Terminationsformel”, die von n abhängt, zusammensetzt. P muß nach einmaliger Ausführung
2.3. SEMANTIK VON WHILE
99
des Schleifenrumpfes für n−1 gültig sein. Nachteil dieser Regelvariante ist, dass eine “herabzählende” Terminierungsformel oft nicht problemadädquat ist und nur unter hohem
Aufwand zu konstruieren ist.
Beispiel 2.50
Das Programm
Sdiv =df
q = 0; r = x; while (r ≥ y) {r = r − y; q = q + 1 }
berechnet in q und r den Quotienten und Rest der ganzzahligen Division von x durch y.
Die totale Korrktheit von
{x ≥ 0 ∧ y > 0} S {x = q y + r ∧ r < y}
kann nachgeweisen werden unter Benutzung der Schleifeninvarianten:
Idiv
=df
(x = q y + r ∧ r > 0)
Mit der W HILEtot -Regel ist der Terminierungsausdruck einfach r. Mit der alternativen
W HILEtot -Regel ist die Definition eines geeigneten Prädikates P (n) aufwendig. In unserem Beispiel:
P (n) =df Idiv ∧ 0 ≤ r − n y < y
{z
}
|
Terminierungssformel
3
Satz 2.51 (Korrektheit des Hoare-Kalk üls für totale Korrektheit)
Mit Hilfe des Kalküls für die totale Korrektheit (d.h. des Kalküls mit der neuen While–
Regel WHILEtot ) lassen sich nur total korrekte Korrektheitsformeln ableiten.
3
Beweis
Der Beweis wird wiederum mittels Induktion nach der Länge der Ableitung geführt. Im
Induktionsschritt beschränken wir uns auf den Fall der WHILEtot –Regel.
W HILEtot
{I ∧ b ∧ (z = t)} S {I ∧ (t < z)}, I ⇒ (t ≥ 0)
{I} while (b) {S} {I ∧ ¬b}
Die anderen Regeln können wie beim Beweis von Satz 2.46 behandelt werden, da die
Termination dort kein Problem ist.
Unter der (Induktions-)voraussetzung:
i) ∀ σ ∈ Ch(I ∧ b ∧ z = t). σ ∈ Def ([[ S ]]) ∧ [[ S ]](σ) ∈ Ch(I ∧ t < z).
ii) ∀ σ ∈ Ch(I). [[ t ]]A (σ) ≥ 0.
100
KAPITEL 2. WHILE–PROGRAMME UND IHRE SEMANTIK
ist zu zeigen, dass für jeden Zustand σ ∈ Ch(I) gilt:
iii) σ ∈ Def ([[ while (b) {S} ]])
iv) [[ while (b) {S} ]]({σ}) ⊆ Ch(I ∧ ¬b)
Punkt (iv) ist bereits im Beweis von Satz 2.46 erledigt worden, so dass nur die Terminierung, also Punkt (iii), noch offen ist.
Sei also σ ∈ Ch(I). Falls die Bedingung b nicht erfüllt ist, also [[ b ]]B (σ) = ff gilt, terminiert die while-Schleife unmittelbar. Im Falle [[ b ]] B (σ) = tt zeigen wir Behauptung (iii)
durch eine verallgemeinerte Induktion über [[ t ]]A (σ). Dabei ist zu beachten, dass wegen
Voraussetzung (ii) dieser Wert nichtnegativ ist. Offenbar terminiert die While-Schleife f ür
Zustand σ genau dann, wenn sie für Zustand σ 0 =df σ{[[ t ]]A (σ)/z} terminiert. Nach
Induktionsannahme terminiert S angesetzt auf σ 0 in einem Zustand σ̃ =df [[ S ]](σ 0 ).
Es gilt:
(ii)
(i)
0 ≤ [[ t ]]A (σ̃) < [[ z ]]A (σ̃)
z ∈F
/ V (S)
=
[[ z ]]A (σ 0 )
Def . σ 0
=
[[ t ]]A (σ)
Nach Induktionsannahme der inneren, also der verallgemeinerten, Induktion terminiert
dann while (b) {S} angesetzt auf σ̃. Also terminiert auch S; while (b) {S} angesetzt auf
σ 0 und somit auch S; while (b) {S} angesetzt auf σ. Wegen
[[ while (b) {S} ]](σ 0 ) = [[ S; while (b) {S} ]](σ 0 )
folgt schließlich Behauptung (iii).
2
Vollständigkeit der Methode von Hoare
Der Beweis der Vollständigkeit der Methode von Hoare, das heißt die Frage, ob auch
alle im Sinne partieller bzw. totaler Korrektheit gültigen Hoare-Tripel im entsprechenden
Hoare-Kalküle bewiesen werden können, ist recht kompliziert und gehört deswegen in eine
Vorlesung des Haupstudiums. An dieser Stelle sei nur folgendes vermerkt:
• Vollständigkeit kann i.allg. nur relativ zu in der Konsequenzregel verwendeten Implikationen gezeigt werden. Man spricht daher von relativer Vollständigkeit.
• Legt man die natürlichen Zahlen als semantischen Bereich zugrunde, so ist der HoareKalkül für WHILE–Programme relativ vollständig.
2.3. SEMANTIK VON WHILE
101
Schlußbemerkung zur axiomatischen Semantik
Zum Abschluß dieses Kapitels, wollen wir noch die Frage klären, warum die Hoaresche
Methode auch axiomatische Semantik genannt wird.
Die Semantik eines Programms ist eine Zustandstransformation auf Σ, wie wir sie schon
bei der operationellen und denotationellen Semantik kennengelernt haben. Nun ist der
Programmentwickler aber nicht an konkreten Zuständen interessiert, sondern an Aussagen P über Zuständen, welche Mengen von Zuständen Ch(P ) charakterisieren. Statt von
Zustandstransformationen wird die Semantik eines Programms hier als Funktion auf der
Potenzmenge von Σ aufgefaßt. Diese Semantik wird formal mit Hilfe des Hoare–Kalk üls
definiert. Da Kalküle aus Axiomen und Regeln aufgebaut sind, wird sie auch axiomatische Semantik genannt.
Zu einem gegebenen Programm haben wir mit der operationellen oder denotationellen
Semantik Berechnungsvorschriften für die Semantik des Programms zur Verfügung. Wie
alle Kalküle liefert der Hoare–Kalkül keine Berechnungsvorschrift für die Programmsemantik und nicht einmal eine Strategie für die Programmverifikation. Das bedeutet, daß
sich der Programmentwickler selber überlegen muß, wie er die Korrektheit des Programms
nachweisen will. Der Hoare–Kalkül liefert dann eine Möglichkeit, die Verifikationsidee zu
formalisieren und ihre Gültigkeit zu beweisen.
Herunterladen