Ausarbeitung - Universität Münster

Werbung
Westfälische Wilhelms-Universität Münster
Ausarbeitung
Algebraische Spezifikation mit OBJ
im Rahmen des Seminars Formale Spezifikation
Andre Christ
Themensteller: Prof. Dr. Herbert Kuchen
Betreuer: Prof. Dr. Herbert Kuchen
Institut für Wirtschaftsinformatik
Praktische Informatik in der Wirtschaft
Inhaltsverzeichnis
1
Motivation
2
Grundlagen algebraischer Spezifikation
2.1 Abstrakte Datentypen . . . . . . . . .
2.2 Syntax abstrakter Datentypen . . . . .
2.3 Order-Sorted Algebra . . . . . . . . .
2.4 Denotationale Semantik . . . . . . . .
2.5 Operationale Semantik . . . . . . . .
2.5.1 Termersetzungssysteme . . .
2.5.2 Gleichungsbasiertes Schließen
3
4
Erweiterte Eigenschaften von OBJ
3.1 Modularisierung . . . . . . . .
3.2 Fehlerbehandlung . . . . . . .
3.3 Generizität . . . . . . . . . . .
3.4 Funktionales Prototyping . . .
3.5 Theorem Proving . . . . . . .
1
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2
2
3
4
7
10
10
13
.
.
.
.
.
14
14
15
16
18
19
Zusammenfassung und Ausblick
22
A Software
A.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
A.2 Installation von OBJ3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
A.3 Installation von BOBJ . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
23
23
24
B Funktionales Prototyping
B.1 Benchmark-System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
B.2 Quelltext Benchmark-Module . . . . . . . . . . . . . . . . . . . . . . . .
B.3 Quelltext Fibonacci-Funktion . . . . . . . . . . . . . . . . . . . . . . . . .
25
25
25
25
i
Kapitel 1: Motivation
1
Motivation
In den vergangenen Jahren ist die Diffusion von Software in nahezu alle Bereiche des täglichen
Lebens fortgeschritten. Angefangen beim klassischen Gebiet der Büro- und Unternehmenssoftware über die Steuerung von Anlagen und Maschinen bis hin zum Einsatz in der Medizin
finden sich Programme, von denen der Mensch abhängig geworden ist. Diese Abhängigkeit
ist eng an die fehlerfreie Funktionsweise, die Korrektheit, gekoppelt. Die zunehmend höhere
Komplexität der Problemstellung und der resultierenden Software, die häufige Änderung
während des Einsatzes und seit jüngerer Zeit die stärker in den Vordergrund rückende Wiederverwendung erfordern grundsätzlich neue Entwicklungsmethoden.
In einem konventionellen Entwicklungsprozess ohne Einsatz formaler Spezifikation folgt
der Realisierung eine intensive Testphase, die bei Fehlern Änderungen der Software mit sich
bringt. Je später Fehler in diesem sich wiederholenden Zyklus entdeckt werden, umso kostenintensiver wird deren Korrektur. Weiterhin ist es wichtig festzuhalten, dass Testen nur
die Existenz von Fehlern aufdeckt, nicht aber fehlerfreie Software garantiert. Als systematischere Herangehensweise zur Qualitätssicherung haben sich daher im Software Engineering
Methoden entwickelt, die dazu dienen, die gewünschte Funktionalität von Programmen vor
der Implementierung festzulegen. Während formale Methoden mathematische Kalküle der
Algebra, Logik oder Mengenlehre benutzen, entziehen sich informale Methoden aufgrund
ihrer textlichen oder grafischen Form axiomatischer oder logischer Verifizierbarkeit.
Der Software-Entwicklungsprozess beim Einsatz formaler Spezifikation ist dadurch gekennzeichnet, immer detailliertere Beschreibungen der zu erstellenden Software zu konstruieren.
Ziel dieses iterativen Prozesses ist ein ausführbares Programm mitsamt seiner Dokumentation. Inhalt der Beschreibungen ist eine Spezifikation der zukünftigen Software. Diese
abstrahiert von der Problemstellung und definiert somit, was getan werden muss, ohne eine
vollständige Beschreibung zu liefern, wie dies erreicht wird. Der schrittweise Übergang einer Spezifikation in eine detailliertere Spezifikation wird unter den Begriff der Verfeinerung
zusammengefasst.
In der Literatur werden modellorientierte Spezifikationen (auch als zustandsorientierte Spezifikationen bezeichnet) und eigenschaftsorientierte Spezifikationen (auch axiomatische oder
algebraische Spezifikationen) unterschieden [LEW96, S. 13 ff.]. Vertreter modellorientierter
Spezifikation sind beispielsweise VDM und Z. In dieser Arbeit wird die algebraische Spezifikation mit OBJ betrachtet. Kapitel 2 führt unter Berücksichtigung der Syntax von OBJ
zunächst die mathematische Basis der algebraischen Spezifikation ein. In Kapitel 3 werden Aspekte von OBJ beleuchtet, die den praktischen Einsatz der Spezifikation mit OBJ
unterstützen. Nach einem zusammenfassenden Beispiel schließt die Arbeit mit einer Bewertung von OBJ.
1
Kapitel 2: Grundlagen algebraischer Spezifikation
2
Grundlagen algebraischer Spezifikation
2.1
Abstrakte Datentypen
Die Aufteilung der Gesamtheit aller möglichen Datenelemente führt zu Klassen gemeinsamer Eigenschaften, den sogenannten Datentypen. Als definierende Eigenschaft eines Datentyps werden die auf einer Wertemenge erklärten Operationen akzeptiert [EGL89, S. 2].
Bei der Analyse der Datentypen liegt der Fokus auf der Betrachtung von abgeschlossenen Familien aufeinander bezogener Datentypen. Das bedeutet, dass alle Datentypen des
Argument- oder Wertebereichs Mitglieder der Familie sind [EGL89, S. 4]. Formal besteht
eine solche Familie aufeinander bezogener Datentypen D1 , ..., Dn aus den Wertemengen
W = W1 , ..., Wn und Operationen mit Argument- und Wertebereich aus W .
In der Mathematik ist eine derartige Struktur als Algebra bekannt. Die Wertemengen nehmen hierbei den Platz der Trägermengen ein, die Operationen entsprechen den Verknüpfungen.
U. a. ist es aus Gründen der Strukturierung sinnvoll, mehrere Datentypen, d. h. n > 1, zu erlauben. Die Algebra ist in diesem Fall heterogen (engl. many-sorted algebra).
Bei der Spezifikation von anwendungsbezogenen Datentypen steht nicht die vollständige
Beschreibung, sondern im Sinne der Abstraktion vielmehr die wesentlichen Eigenschaften
des Datentyps im Vordergrund. Daher spricht man in dieser Phase der Softwareentwicklung
von einem abstrakten Datentyp (ADT). Der Prozess der Verfeinerung engt diese Freiheiten
im Zuge des Entwicklungsprozesses bis hin zur Implementation wieder ein [EGL89, S. 5].
Anhand des ADT Stack natürlicher Zahlen, vgl. Listing 1, werden im Folgenden Begriffe
der algebraischen Spezifikation unter Benutzung der Syntax von OBJ eingeführt. Als Operationen des Stacks sollen push, top und pop sowie die Konstante empty definiert werden.
obj STACK-OF-NAT is
protecting NAT .
sorts Stack .
op empty : -> Stack .
*** Signatur
op push : Nat Stack -> Stack .
op top : Stack -> Nat .
op pop : Stack -> Stack .
var X : Nat . var S : Stack . *** Axiome
eq top(push(X, S)) = X .
eq pop(push(X, S)) = S .
endo
Listing 1: ADT Stack natürlicher Zahlen.
2
Kapitel 2: Grundlagen algebraischer Spezifikation
Das Schlüsselwort sorts listet diejenigen Datentypen auf, die in die Beschreibung eingehen. Die Operationen und Sorten des OBJ-Moduls NAT, welches die natürlichen Zahlen
spezifiziert, werden durch is protecting importiert. Diese Bestandteile von Modularisierung und hierarchischer Strukturierung werden aus Gründen der Übersichtlichkeit erst im
späteren Verlauf dieser Arbeit beschrieben.
Das Schlüsselwort op ist jeweils dem Namen der verfügbaren Operationen vorangestellt.
Nach dem Doppelpunkt hinter dem Namen folgen Argumentliste und Rückgabewert, die
durch einen Pfeil voneinander abgetrennt sind. Dieser erste Teil der Spezifikation legt mittels
Signatur die Syntax des abstrakten Datentyps fest.
Im zweiten Teil werden unter Benutzung von Variablen, angeführt durch var, mittels Gleichungen nach eq die semantischen Eigenschaften beschrieben. Durch die Angabe von Axiomen beschränkt sich der Spezifizierer auf die gewünschte Funktionalität, er abstrahiert von
einer konkreten Umsetzung und deren Details [HL89, S. 8 f.].
Signatur und Gleichungen machen zusammen eine Spezifikation aus. Eine Spezifikation definiert einen abstrakten Datentyp, sie kann von einer Algebra im Sinne einer Interpretation erfüllt werden. Die algebraische Spezifikation basiert auf einem mathematischen Grundgerüst. Es erlaubt unter anderem, eine eindeutige Beziehung zwischen Signatur und Algebra
herzustellen. Wichtigstes Ziel dieses und des folgenden Kapitels ist es, das Fundament für
gleichungsbasiertes Schließen, einer Technik zur Programmverifikation, zu legen.
2.2
Syntax abstrakter Datentypen
Definition 2.1
Eine Signatur Σ besteht aus einer Menge S von Sorten und einer S ∗ × S-indizierten Mengenfamilie {Σs̄,s |s̄ ∈ S ∗ , s ∈ S} von Operationen [GM96, S. 12].
Dies bedeutet, dass zu jeder Liste von Argumentsorten s̄ = s1 , ..., sn und jeder Ergebnissorte
s in Σ eine Menge Σs̄,s von Operationen existiert, wobei die leere Menge erlaubt ist. Eine
Operation σ ∈ Σs̄,s wird auch geschrieben als σ : s1 × ... × sn → s. Die Anzahl der Argumente n gibt die Stelligkeit (engl. arity) der Operation an. Bei einer leeren Argumentliste,
d. h. n = 0, wird die Operation als Konstante der Sorte s bezeichnet.
Die Operation push des ADT Stack ist hiernach zweistellig: push : N at × Stack → Stack.
Dahingehen ist empty eine Konstante vom Typ Stack: empty :→ Stack.
Standardmäßig haben Operationen in OBJ die Prefix-Form mit Klammern und kommagetrennten Argumenten (z. B. push des ADT Stack). OBJ erlaubt darüber hinaus Prefix-,
3
Kapitel 2: Grundlagen algebraischer Spezifikation
Infix- und Postfix-Form. Möglich wird diese sogenannte Mixfix-Form durch Unterstriche
als Platzhalter für Variablen. In Listing 2 wird der ADT Natürliche Zahlen in Peano-Notation
definiert. Diese Notation geht auf den Mathematiker Peano zurück, der gezeigt hat, dass sich
die Menge der natürlichen Zahlen mit einem konstanten Startelement, hier der 0 und einer
Nachfolger-Operation s beschreiben lässt. Die Zahl zwei entspricht in dieser Notation beispielsweise s(s(0)). Für die Additions- und Multiplikations-Operation wird im Beispiel
die Infix-Notation verwendet, für die Nachfolger-Operation die standardmäßige Form.
obj NAT is
sort Nat .
op 0 : -> Nat .
op s : Nat -> Nat [prec 15] .
op _+_ : Nat Nat -> Nat [prec 40] .
op _*_ : Nat Nat -> Nat [prec 30] .
[...]
endo
Listing 2: ADT Natürliche Zahlen in Peano-Notation (Ausschnitt).
Um die Notwendigkeit von Klammerung in Ausdrücken einzuschränken, kann einer Operation per Attribut [prec X] ein Präzendenz-Wert zugeordnet werden, der die Bindungsstärke angibt. Dieser liegt zwischen 0 und 128, wobei 0 die höchste und 128 die schwächste
Bindung angibt. Eine unäre (einstellige) Prefix-Operation hat standardmäßig den Wert 15,
eine binäre (zweistellige) den Wert 41. Der Ausdruck s(0)+ s(0)* s(s(0)) wird unter
Benutzung der Präzendenz-Werte im Beispiel gemäß der üblichen Punkt-vor-Strich-Regel ganz ohne Klammerung - richtig interpretiert.
2.3
Order-Sorted Algebra
Definition 2.2
Sei Σ eine Signatur, dann besteht jede Σ-Algebra aus einer Trägermenge As für jede Sorte
s ∈ S, einer Funktion aσ : As1 × ... × Asn → As für jede Operation σ ∈ Σs̄,s mit s̄ 6= [] und
einer Konstanten aσ ∈ As für jedes σ ∈ Σ[],s [GM96, S. 16].
Die Definition legt eine Algebra als Interpretation oder Modell einer bestimmten Signatur
fest. Sorten werden durch Mengen interpretiert, Operationen durch Funktionen oder Konstanten mit Argumenten und Rückgabewerten der entsprechenden Sorten. Gemäß OrderSorted Algebra (OSA), einer Erweiterung von Many-Sorted Algebra, können Sorten partiell
geordnet sein (reflexive, transitive und antisymmetrische Relation). Auf die Trägermengen
bezogen bedeutet partiell geordnet, dass eine Menge die Untermenge in einer anderen Menge
4
Kapitel 2: Grundlagen algebraischer Spezifikation
ist. Laut Zahlentheorie der Mathematik gilt beispielsweise, dass die Menge der natürlichen
Zahlen Nat eine Untermenge der ganzen Zahlen Int ist. In einem OBJ-Modul wird dies
durch subsort Nat < Int ausgedrückt. Auf der semantischen Ebene der Algebra lautet
die Relation der entsprechenden Trägermengen AN at ⊆ AInt . In der Sortenhierarchie ist
Mehrfachvererbung möglich. Das bedeutet, dass eine Sorte mehrere direkt übergeordnete
Sorten haben kann. Als Vorteil einer solchen Sortenhierarchie wird im Verlauf dieser Arbeit
die Fehlerbehandlung mit OBJ näher beleuchtet [GWM+ 00, S. 5].
Wie in anderen Programmiersprachen (z. B. C++ oder Java) sind in OBJ polymorphe Operationen möglich. Der Begriff Polymorphie wird im Kontext von Operationen allgemein so
verstanden, dass es für ein Operationssymbol mehrere Bedeutungen geben kann. Stehen die
Argument- und Ergebnissorten der Operationen nicht in Relation zueinander, so sprechen
[GM92] von Ad-hoc-Polymorphismus“. Das Beispiel eines polymorphen Operators + für
”
die Addition natürlicher Zahlen und die Konkatenation von Strings, vgl. Listing 3, illustriert
dies.
op _+_ : Nat Nat -> Nat .
op _+_ : String String -> String .
Listing 3: Ad-hoc-Polymorphismus am Beispiel der Additions-Operation.
Sind Argument- und Ergebnissorten der Operationen mit gleichem Operations-Symbol zudem hierarchisch angeordnet, z. B. Nat < Int, so heißt diese Art der Überladung nach
[GM92] Subsort-Polymorphismus“. In Listing 4 ist dieser Polymorphismus am Beispiel
”
des Operators + angewendet. Gemäß der Zahlenhierarchie sind die natürlichen Zahlen Nat
als Untermenge der ganzen Zahlen Int definiert. Die Additions-Operation benutzt sowohl
für natürliche als auch ganze Zahlen das Symbol +. Aufgrund der Hierarchie der Sorten ist
Op 2 eine Spezialisierung von Op 1 derart, dass das Ergebnis der Summe zweier natürlicher
Zahlen wieder eine natürliche Zahl ist. Die gemischte Summe ist hingegen eine ganze Zahl.
subsort Nat < Int .
op _+_ : Int Int -> Int . *** Op 1
op _+_ : Nat Nat -> Nat . *** Op 2
Listing 4: Subsort-Polymorphismus am Beispiel der Additions-Operation.
Auf der Ebene der Algebra ist diejenige Funktion, die die Operation mit der bzgl. der Sortenhierarchie kleineren Argumentsorte interpretiert, einschränkend gegenüber der Funktion, die
die Operation mit der größeren Argumentsorte interpretiert. Dies bedeutet, dass die Addition
natürlicher Zahlen (Op 2) zum gleichen Ergebnis führen muss, wie die Integer-Addition (Op
1) der gleichen natürlichen Zahlen [GWM+ 00, S. 8].
Zwischen einer Signatur und einer Algebra ist keine eindeutige Eins-zu-Eins Verbindung
möglich. Mehrere Signaturen können eine Algebra beschreiben, eine Signatur kann durch
5
Kapitel 2: Grundlagen algebraischer Spezifikation
mehrere Algebren interpretiert werden. Für den ADT der natürlichen Zahlen mit der Signatur ΣN AT lautet eine ΣN AT -Algebra A wie folgt: Die Trägermenge AN at = {0, 1, 2, ...}
enthält als Datenobjekte die natürlichen Zahlen, die Konstante ist als a0 = 0 definiert und
der Operator s als as (n) = n+1. Als Algebra für die natürlichen Zahlen wird diese Interpretation zweifelsohne als die nächstliegende anerkannt werden. Sie wird daher auch StandardInterpretation genannt [GM96, S. 26]. Jedoch ist sie nicht die einzige: BN at = {0, 1}, b0 = 0
und bs (n) = 1 − n ist ebenfalls eine gültige Algebra.
Für eine bestimmte Klasse von Modellen lässt sich hingegen keine Standard-Interpretation
ausmachen. In OBJ wird dieser Unterscheidung Rechnung getragen: Ein OBJ-Modul wird
durch die Schlüsselwörter obj und endo eingeschlossen, wenn eine Standard-Interpretation
beabsichtig ist. Es handelt sich dann um ein Objekt. Soll andernfalls eine Menge von Algebren beschrieben werden, so heißt das Modul Theorie und wird durch die Schlüsselwörter
th und endth eingeschlossen.
Eine Denotation ist eine Abbildung δ : Σ → A vom syntaktischen Bereich der Signatur
Σ in den semantischen Bereich der Algebra A. Darauf aufbauend lassen sich mithilfe der
folgenden Definition die Beziehungen zwischen zwei Algebren beschreiben.
Definition 2.3
A und B seien Algebren zur gleichen Signatur Σ mit den Denotationen δA : Σ → A und
δB : Σ → B. Ein Homomorphismus h : A → B ist eine Menge von Abbildungen h :
δA (s) → δB (s). Für jede Sorte s existiert eine Abbildung h, die den Datenobjekten der
Algebra A die Objekte der Algebra B zuordnet, so dass gilt [HL89, S. 20]:
1. Für jede nullstellige Operation σi :→ s: h(δA (σi )) = δB (σi ).
2. Für jede mehrstellige Operation σi : s1 ×...×sn → s mit n > 0 und den passend getypten Objekten t1 , ..., tn die zu den Mengen δA (s1 ), ..., δA (sn ) gehören: h(δA (σi )(t1 , ..., tn )) =
δB (σi )(h1 (t1 ), ..., hn (tn )).
Ein Homomorphismus von A nach B wird nach dieser Definition so verstanden, dass jeder
Operation von A die entsprechende Operation von B zugeordnet wird. Gilt jede Abbildung
h auch bijektiv, so handelt es sich um einen Isomorphismus zwischen den beiden Algebren
[HL89, S. 20]. Abgesehen von der Benennung der Objekte sind die Algebren dann identisch.
Das kommutative Diagramm in Abbildung 1 illustriert die Beziehung der Algebren anhand
der mehrstelligen Operation σi . Kommutativ bedeutet in diesem Zusammenhang, dass das
Resultat h(t) gleich bleibt, unabhängig davon, ob die Operation σi oder die Abbildung h
zuerst angewendet wird [EGL89, S. 23].
6
Kapitel 2: Grundlagen algebraischer Spezifikation
A
B
h1 ,...,hn
(t1 , ..., tn ) −−−−→ (h1 (t1 ), ..., hn (tn ))


δ (σ )
δ (σ )
yB i
yA i
t
h
−−−→
h(t)
Abbildung 1: Kommutatives Diagramm der Algebren A und B.
2.4
Denotationale Semantik
Um der Spezifikation eine semantische Bedeutung zu geben, reicht die Betrachtung der einzelnen Operationen nicht aus. Erst die Kombination zu Termen und das Aufstellen der Gleichungen erlaubt es letztendlich, die Eigenschaften zu beschreiben. Jedem syntaktischen Objekt wird ein mathematisches, semantisches Objekt zugeordnet. Dies wird auch Denotation
genannt, weshalb insgesamt von der denotationalen Semantik gessprochen wird. Die Menge
der Gleichungen einer Spezifikation werden auch Axiome genannt.
Um gültige Terme aufzustellen, bedarf es einer Termsprache:
Definition 2.4
Sei Σ eine Signatur mit einer Menge von Sorten S und X eine Menge getypter Variablen,
dann ist eine Termsprache TΣ wie folgt definiert [HL89, S. 28]:
1. Jede Variable xi vom zugehörigen Typ si ist ein Σ-Term vom Typ si .
2. Jede Konstante σi :→ s ist ein Σ-Term vom Typ s.
3. Sei σi : s1 × ... × sn → s mit n > 0 und t1 , ..., tn Terme der Sorten s1 , ..., sn , dann ist
σ(t1 , ..., tn ) ein Σ-Term vom Typ s.
4. Jedes weitere Element der Termsprache kann in einer endlichen Anzahl von Schritten
so abgeleitet werden.
Die Termsprache die durch den ADT Stack definiert wird, enthält unter anderem folgende
Terme: {empty, push(X, empty), top(push(X, empty)), ...}.
Nachdem eine OBJ-Datei geladen wurde, kann am OBJ-Prompt1 mittels parse überprüft
werden, ob es sich um einen gültigen Term handelt. Eine OBJ-Datei wird durch den Befehl in Name geladen. Der Name entspricht dem Dateinamen ohne dessen Endung. Der
parse Ausdruck arbeitet immer auf dem zuletzt geladenen Modul, welches durch open
Modulname, z. B. open STACK-OF-NUMBER, geändert werden kann. Weiterhin lässt sich
1
Als OBJ-Implementationen kommen in dieser Arbeit OBJ3 und BOBJ zum Einsatz, vgl. Anhang.
7
Kapitel 2: Grundlagen algebraischer Spezifikation
über die Ausgabe sicherstellen, das der Parser die Bindungsreihenfolge (Auswertung des
Präzedenz-Wertes, vgl. Kapitel 2.2) gemäß Intention interpretiert. Die Ausgabe erfolgt bei
BOBJ in einer Baumstruktur, vgl. Abbildung 2.
top : NeStack -> Nat
push : Nat Stack -> NeStack
var X : Nat
empty : -> Stack
Abbildung 2: Ausgabe als Baumstruktur von parse top(push(X, empty)).
Eine Spezifikation (Σ, E) ist eine Signatur Σ, die um eine Menge von Gleichungen E erweitert wurde. In OBJ gibt es zwei Gleichungstypen für die grundsätzlich gilt, dass alle
verwendeten Variablen im Modul deklariert werden müssen.
Eine Gleichung hat die Form eq t1 = t2 mit t1 , t2 als gültigen Termen der gleichen Sorte
aus der Termsprache TΣ . In bestimmten Fällen kann es hilfreich sein, dass eine Gleichung
nur unter einer gegebenen Bedingung als wahr gilt. Dieser zweite Typ bildet die bedingten
Gleichungen der Form cq t1 = t2 if t3 mit t1 , t2 , t3 ∈ TΣ . Es gelten hierbei dieselben Regeln
wie bei einfachen Gleichungen mit der Ergänzung, dass der dritte Term von der Sorte Bool
stammt.2 Die Gleichung t1 = t2 muss nur dann gelten, wenn t3 wahr ist [GWM+ 00, S. 10].
Auf der Ebene der Algebra kann nun bestimmt werden, ob diese eine Spezifikation (Σ, E)
erfüllt. Folgendes Beispiel verdeutlicht, dass dies offensichtlich von der Wahl der Algebra
abhängt. Sei Σ eine Algebra mit der binären Infix-Operation +, As = {a, b} die Trägermenge
einer zugehörigen Algebra und a+ eine Verknüpfung zur Konkatenation von Listen. Mit
X, Y als Variablen der Sorte Liste lautet das kommutative Gesetz wie folgt: X +Y = Y +X.
Dieses wird von dieser Algebra jedoch nicht erfüllt, denn für z. B. X = ab und Y = ba gilt
X + Y = abba 6= baab = Y + X [HL89, S. 25].
Um zu zeigen, dass eine Gleichung erfüllt ist, wird mithilfe des Begriffs der Substitution
festgelegt, wie mit Variablen in Termen zu verfahren ist.
Definition 2.5
Sei TΣ die Menge der Terme über eine Signatur Σ und X die Menge getypter Variablen, dann
ist eine Substitution wie folgt definiert: θ : X → TΣ , wobei x ∈ X und θ(X) denselben Typ
haben. Eine Grundsubstitution liegt vor, wenn eine Substitution zu einem variablenfreien
Term führt [HL89, S. 28].
Abbildung 3 veranschaulicht anhand des Terms push(x, y) aus dem ADT Stack zwei
2
Die Sorte Bool gehört zu den vordefinierten Sorten in OBJ, die standardmäßig zur Verfügung stehen. Bool
wird darüber hinaus automatisch in alle OBJ-Module importiert.
8
Kapitel 2: Grundlagen algebraischer Spezifikation
Substitutionen bis hin zu einem variablenfreien Term.
push(x, y)

x → s(0)
y
push(s(0), y)

y → push(0,
y
empty)
push(s(0), push(0, empty))
Abbildung 3: Substitution des Terms push(x, y) des ADT Stack.
Auf der bisher beschriebenen mathematischen Grundlage kann nun ausgedrückt werden,
wann eine Algebra A mit einer Denotation δA : Σ → A die Spezifikation (Σ, E) erfüllt. A
erfüllt eine Gleichung e ∈ E : t1 = t2 falls δA (t1 ) = δA (t2 ) für alle Grundsubstitutionen gilt.
A erfüllt eine bedingte Gleichung e ∈ E : t1 = t2 if t3 falls δA (t1 ) = δA (t2 ) für alle Grundsubstitutionen gilt, für den Fall, in dem δA (t3 ) = true ist. A erfüllt die Spezifikation, falls
alle Gleichungen derart erfüllt werden [GM96, S. 25 f.]. Der Begriff der Erfülltheit ist also
auf der Ebene der Objekte der Algebra definiert. Linke und rechte Seite der variablenfreien
Terme sind genau dann gleich, wenn diese jeweils das gleiche Objekt denotieren.
Am Beispiel der Signatur der natürlichen Zahlen wurde in Kapitel 2.3 bereits informal eingeführt, dass es mehrere Algebren zu einer Spezifikation geben kann. Diese Eigenschaft ist
auch unter dem Begriff der Varietät bekannt. In OBJ wird der Ansatz der initialen Algebra verfolgt, um die Standard-Interpretation zu identifizieren. Die zugrunde liegende Idee
ist, dass zwei variablenfreie Terme verschiedene Objekte denotieren, falls auf der Basis der
Axiome nicht gezeigt werden kann, dass sie gleich sind. Sie ist wie folgt definiert:
Definition 2.6
Sei Σ eine Signatur und E eine Menge von Σ-Gleichungen dann ist A eine initiale ΣAlgebra, wenn folgende Eigenschaften gelten [GM96, S. 36]:
1. no junk: Jedes Objekt von A kann durch einen Σ-Term repräsentiert werden.
2. no confusion: Für jede Gleichung variablenfreier Terme kann mithilfe der Axiome
gezeigt werden, dass A sie erfüllt.
Die Definition der Isomorphie, vgl. Definition 2.3, ermöglicht es, von der“ initialen Alge”
bra zu sprechen, weil verschiedene Benennungen der Objekte auf Ebene der Algebra keine
Rolle spielen: Sei I eine initiale Algebra, dann gehören alle Algebren der Varietät zu dieser
Klasse der initialen Algebra, wenn sie isomorph zu I sind. Hier findet sich das Prinzip der
Abstraktion: Die Implementierung ist unerheblich, solange die Resultate übereinstimmen.
9
Kapitel 2: Grundlagen algebraischer Spezifikation
2.5
2.5.1
Operationale Semantik
Termersetzungssysteme
Bei der algebraischen Spezifikation werden Programme durch Terme repräsentiert. Über das
im vorangegangenen Kapitel vorgestellte Modell hinaus stellen Termersetzungssysteme
einen Algorithmus bereit, der das Ergebnis eines Terms berechnet. Gemäß ihres ausführbaren
Charakters ist diese Eigenschaft unter dem Namen operationale Semantik bekannt [GM96,
S. 36].
Eine Anfrage an das Termersetzungssystem wird mit dem Befehl reduce ausgeführt (wie
parse bezieht sich die Anfrage auf das aktuell geöffnete Modul). Eine Anfrage bzgl. des
ADT Stack, bei der nacheinander die Peano-Zahlen null und eins auf dem Stack abgelegt
werden, lautet reduce top(push(s(0), push(0, empty))). Das System liefert mit
Nat: s(0) die korrekte Antwort. Aufbau und Funktionsweise des Termersetzungssystems
sind Gegenstand dieses Kapitels.
Ein Termersetzungssystem (engl. Term Rewriting System) besteht aus einer endlichen Menge an Termersetzungsregeln t1 ⇒ t2 . Damit eine Gleichung t1 = t2 eine Termersetzungsregel im Sinne von OBJ ist, müssen folgende Regeln erfüllt sein [GWM+ 00, S. 9]:
1. Die Terme beider Seiten haben die gleiche oder eine gemeinsame übergeordnete Sorte.
2. Die Sorte des linken Terms (engl. Left Hand Side“ (LHS)) ist größer oder gleich der
”
Sorte des rechten Terms (engl. Right Hand Side“ (RHS)).
”
3. Alle Variablen, die auf der rechten Seite eines Terms benutzt werden, sind auch auf
der linken Seite des Terms vorhanden.
4. Der linke Term ist keine Variable.
Diese Art der Spezifikation, bei denen Termersetzungssysteme zum Einsatz kommen, wird
auch konstruktive Spezifikation genannt. Sie hat den Vorteil, dass sie meist direkt ausgeführt werden kann und somit Rapid Prototyping unterstützt [HL89, S. 120].
Für eine konstruktive Spezifikation ist die Aufteilung der Operatoren in zwei Gruppen charakteristisch. Die Gruppe der Konstruktoren generiert Objekte des abstrakten Datentyps,
wohingegen die verbleibenden Operationen, auch Selektoren, das funktionale Verhalten der
Objekte beschreiben [HL89, S. 123]. Im Beispiel des ADT Stack sind empty und push
Konstruktoren sowie pop und top Selektoren. In OBJ ist es im Gegensatz zu anderen Spezifikationssprachen, wie z. B. Maude, nicht nötig, die Konstruktoren durch ein Attribut zu
10
Kapitel 2: Grundlagen algebraischer Spezifikation
identifizieren. Beim Aufstellen der Axiome ist die Unterscheidung der Operationen jedoch
sinnvoll.
Es ist ein typisches Muster zum Aufstellen der Axiome, für jedes Paar aus Konstruktor
und zugehörigem Selektor eine Gleichung zu bilden. Der linke Term der Gleichung besteht bei dieser Vorgehensweise aus einem Selektor, der einen Konstruktor als Argument
hat. Die Form der Gleichungen, insbesondere der Gebrauch von Variablen, unterliegt gewissen Beschränkungen. Die oben aufgeführten Regeln für das Aufstellen der Gleichungen sind
deshalb auch als Konstruktivitätsbedingungen bekannt. Beim ADT Stack wurden nur Gleichungen aus Paaren der Selektoren top und pop mit dem Konstruktor push gebildet. Die
ungültige Anwendung von top und pop auf den leeren Stack, empty, wird im Rahmen der
Fehlerbehandlung mit OBJ in Kapitel 3.2 behandelt.
Die Funktionsweise der Termersetzung soll der Einfachheit halber am Beispiel des ADT
Natürliche Zahlen, vgl. Listing 5, durchgeführt werden. Das entsprechende OBJ-Modul wurde dazu um Gleichungen ergänzt (vgl. ursprüngliches Listing 2 in Kapitel 2.2). Sie erfüllen
die Konstruktivitätsbedingungen und sind daher gültige Termersetzungsregeln.
obj NAT is
sort Nat .
op 0 : -> Nat .
op s : Nat -> Nat [prec 15] .
op _+_ : Nat Nat -> Nat [assoc comm prec 40] .
op _*_ : Nat Nat -> Nat [assoc comm prec 30] .
vars X Y : Nat .
eq 0 + X = X .
eq s(X) + Y = s(X + Y) .
eq 0 * X = 0 .
eq s(X) * Y = (X * Y) + Y .
endo
*** 1
*** 2
*** 3
*** 4
Listing 5: ADT Natürliche Zahlen in Peano-Notation.
Die Prozedur lautet wie folgt: Für eine Signatur Σ sei t0 ein zu reduzierender Term aus der
Termsprache t0 ∈ TΣ gegeben. Gemäß Definition 2.5 sei θ eine Substitution, um Bindungen
zwischen Variablen und Termen herzustellen. Eine Termersetzungsregel ei : tl ⇒ tr kann
auf den Teilterm t00 von t0 angewendet werden, falls die Variablen der linken Seite der Termersetzungsregel so substituiert werden können, dass θ(tl ) = t00 gilt. t00 wird dann durch die
substituierte Rechte Seite θ(tr ) der Regel in t0 ersetzt. Die Menge der Teilterme schließt auch
t0 selbst mit ein. Es wird dann von einer direkten Übereinstimmung gesprochen [GWM+ 00,
S. 12]. Insbesondere für Übereinstimmungen (engl. matches) von Teiltermen ist es notwendig, dass die Substitutionen im Kontext des zu reduzierenden Terms stattfinden. Denn es ist
11
Kapitel 2: Grundlagen algebraischer Spezifikation
möglich, dass eine Ersetzungsregel in einer mehrschrittigen Regelanwendung mehrfach mit
verschiedenen Variablenbindungen vorkommt [GM96, S. 31]. Eine Anwendung einer Regel
ei auf zwei Terme wird im Folgenden durch das Symbol t0 ⇒ei t1 ausgedrückt.
Wird durch die Abfolge von Termersetzungen ein Term erreicht, auf den keine Termersetzungsregel angewendet werden kann, dann ist der Term in Normalform. Dieser Term ist das
Ergebnis der Berechnung eines Ausdrucks [GM96, S. 32].
Die Termersetzung am Beispiel:3
0 + s(0) * 0 ⇒e1 s(0) * 0 ⇒e4 0 * 0 + 0 ⇒e3 0 + 0 ⇒e1 0
Wenn durch eine Variablensubstitution eine Übereinstimmung mit der linken Seite einer bedingten Termersetzungsregel ei : tl ⇒ tr if tc gefunden wurde, verläuft die Prozedur ein
wenig anders. Es werden die Variablen der Bedingung tc mit derselben Substitution gebunden und überprüft, ob der Term entsprechend der Bool-Sorte wahr wird. Nur dann findet die
Ersetzung durch die rechte Seite statt. Die Bindung der Variablen innerhalb des Kontextes
ist auch hier zwingend notwendig. Die Auswertung der Bedingung kann nämlich zu weiteren Ersetzungsschritten führen, wobei es z. B. möglich ist, dass die ursprüngliche bedingte
Ersetzungsregel erneut angewendet wird [GWM+ 00, S. 13].
Die Termersetzung läuft in der Regel nicht so trivial wie in obigem Beispiel ab. Es sind
bestimmte Eigenschaften wünschenswert. Ein Termersetzungssystem erfüllt die ChurchRosser Eigenschaft, falls die Reihenfolge der Termersetzungen keine Rolle spielt. Wird ein
Term t0 auf zwei verschiedene Weisen zu den Termen t1 und t2 umgeschrieben, dann gibt
es einen Term t3 , zu dem t1 und t2 umgeschrieben werden können. Das System terminiert,
wenn es keine endlose Abfolge von Termersetzungen gibt, wie z. B. t0 ⇒e1 t1 ⇒e2 ... .
Wenn ein Termersetzungssystem Church-Rosser ist und terminiert, dann heißt es kanonisch
[GM96, S. 37].
Diese Eigenschaft ist jedoch unentscheidbar. Mittels Algorithmus (Knuth-Bendix) lässt sich
nur für ein terminierendes System zeigen, dass es zu einem eindeutigen Ergebnis führt. Laut
den Autoren von OBJ stellt dieses Entscheidungsproblem jedoch kein Problem dar, da das
resultierende Termersetzungssystem nahezu immer kanonisch ist. Der Grund dafür ist, dass
die Gleichungen meist intuitiv primitiven Rekursionen folgen [GWM+ 00, S. 17].
Bei binären Operationen können die Eigenschaften der Assoziativität, Identität und Kommutativität direkt als Attribute in der Signatur gesetzt werden, und nicht als Regeln in den
3
Die Unterstriche verdeutlichen, ob es sich um die Übereinstimmung eines Teilterms oder des ganzen Terms
handelt.
12
Kapitel 2: Grundlagen algebraischer Spezifikation
Axiomen. Sie betreffen dann nicht nur die Syntax (Parsen von Ausrücken), sondern auch die
Semantik (Reihenfolge der Auswertung).
Für die Assoziativität hat das Attribut assoc in op _+_ : Nat Nat -> Nat [assoc]
die gleiche Bedeutung wie eq (M + N)+ P = M + (N + P). Mit dem Attribut id:
0 kann ein Term angegeben werden, hier 0, für den die Identität der Operation gilt. Die
Identität würde für der Addition der Gleichung eq M + 0 = M entsprechen. Das Attribut
comm ersetzt die Bedeutung der Gleichung eq M + N = N + M. Letztere würde zu einer
nicht terminierenden Abfolge von Ersetzungen führen a + b ⇒ b + a ⇒ a + b ⇒ ... . Es
ist daher zwingend notwendig, die Attribute den Gleichungen vorzuziehen, da sie nicht als
normale Termersetzungsregeln ausgewertet werden [GM96, S. 33].
2.5.2
Gleichungsbasiertes Schließen
Während beim Termersetzungsverfahren die Ersetzungsregeln nur von links nach rechts,
oder auch vorwärts“, angewendet werden, sind beim gleichungsbasierten Schließen bei”
de Richtungen möglich. Unter Zuhilfenahme von Schlussregeln (Reflexivität, Symmetrie,
Transitivität, Substituierbarkeit und Kongruenz) können aus der Menge der Gleichungen E
neue Axiome abgeleitet werden. Weil die Anwendung der Regeln von rechts nach links nicht
automatisiert ablaufen kann, stellt OBJ Befehle zur manuellen Anwendung bereit (apply,
start) [GWM+ 00, S. 50].
Da in dieser Arbeit der Fokus auf Termersetzungssystemen als operationale Semantik liegt,
wird auf eine detaillierte Betrachtung verzichtet. Es ist aber anzumerken, dass das gleichungsbasierte Schließen einen Vorteil bzgl. des mathematischen Modells bietet: Jede Gleichung, die bzgl. aller gültigen Modelle wahr ist, kann auch hergeleitet werden. Termersetzungssysteme erfüllen diese Vollständigkeit aufgrund ihrer gerichteten Regeln nur selten
[GM96, S. 32].
13
Kapitel 3: Erweiterte Eigenschaften von OBJ
3
Erweiterte Eigenschaften von OBJ
3.1
Modularisierung
Modularisierung bezeichnet die Zerlegung von Problemen in Teilprobleme bis diese eine angemessene Größe zur Lösung des Problems darstellen. Aus diesem Devide and Conquer“”
Ansatz ergeben sich Vorteile für den gesamten Entwicklungsprozess: Erst die Zerlegung in
Teilprobleme macht eine verteilte Bearbeitung durch mehrere Personen möglich. Durch Reduktion der Problemgröße lässt sich dieses darüber hinaus einfacher erfassen. In OBJ wird
dieser Ansatz durch eine Modulstruktur unterstützt, in der eine Art Vererbung der Definitionen möglich ist [LEW96, S. 9].
Bei der Definition eines neuen OBJ-Moduls ist es möglich, bereits definierte Module zu importieren. Dies wurde bereits beim ADT Stack natürlicher Zahlen benutzt, in welches der
ADT Natürliche Zahlen durch das Schlüsselwort protecting importiert wurde. Importe
verhalten sich transitiv in folgendem Sinn: Wenn ein Modul M ein Modul M 0 importiert,
welches wiederum M 00 importiert, dann wird M 00 auch in M importiert. Es können gleichzeitig mehrere Module importiert werden [GWM+ 00, S. 26].
Es existieren im wesentlichen drei verschiedene Arten, wie die Definitionen aus M 0 in das
andere Modul M übernommen werden. Diese unterscheiden sich bezüglich der Einhaltung
von Eigenschaften der initialen Algebra beim Import. No junk: Es werden keine neuen Objekte für die Sorten von M 0 durch Konstruktoren in M hinzugefügt. No confusion: Die Bedeutung von Operatoren aus M 0 wird nicht durch neue Gleichungen verändert, d. h. in M
wird keine Gleichheit für ungleiche Objekte in M 0 eingeführt [GM96, S. 41].
Die möglichen Import-Arten in OBJ sind:
1. protecting: Garantiert no junk und no confusion durch Import. Die grundsätzliche
Idee ist, die Bedeutung des importierten Moduls nicht zu verändern.
2. extending: Garantiert no confusion durch Import. Es ist möglich, den Sorten von M 0
neue Objekte hinzuzufügen.
3. including: Keine Garantie.
OBJ verfügt über eine Reihe vordefinierter Module von Standardtypen (u. a. BOOL, NAT, INT
und FLOAT), die sich bei der Spezifikation ohne Einschränkungen importieren lassen. Weiterhin besteht die Möglichkeit, ein bereits definiertes Modul (einschließlich der vordefinierten
Module) zu überschreiben, indem ein neues Modul mit dem gleichen Namen definiert wird.
14
Kapitel 3: Erweiterte Eigenschaften von OBJ
3.2
Fehlerbehandlung
Bei der Definition von abstrakten Datentypen kommt es vor, dass Operationen für bestimmte
Wertebereiche nicht definiert sind. Ein typisches Beispiel beim ADT Stack ist die Anwendung der Operationen top und pop auf den leeren Stack. In Pseudo-Spezifikationssprachen
findet sich dann häufig unter den Axiomen die Gleichung top(empty)=undefined, die
Interpretation dieses Ausdrucks ist jedoch unklar. Aufgrund der partiell geordneten Sortenhierachie (Order-Sorted Algebra) bietet OBJ mit Retracts ein Konzept, dass den Fehlerfall
besser in die Spezifikation integriert [GWM+ 00, S. 15].
Der ADT Stack natürlicher Zahlen wird so modifiziert, dass NeStack eine Untersorte von
Stack ist, vgl. Listing 6.4 NeStack hat die Bedeutung eines Stacks mit mindestens einem
Element. Zu beachten ist, das top und pop nun als Argumentsorte jeweils NeStack haben,
und push die Rückgabesorte NeStack, also den nichtleeren Stack, hat.
obj STACK-OF-NAT is
protecting NAT .
sorts Stack NeStack .
subsort NeStack < Stack .
op empty : -> Stack .
op push : Nat Stack -> NeStack .
op top : NeStack -> Nat .
op pop : NeStack -> Stack .
var X : Nat . var S : Stack .
eq top(push(X, S)) = X .
eq pop(push(X, S)) = S .
endo
Listing 6: ADT Stack natürlicher Zahlen mit Retracts.
Für die zwei Sorten NeStack und Stack fügt OBJ automatisch eine Retract-Operation mit
einer Gleichung ein. Retract und Gleichung sind in Listing 7 expliziert. Die Gleichung wird
nur dann wahr, wenn das Argument ein Objekt der Untersorte NeStack ist.
op r:Stack>NeStack : Stack -> NeStack .
var X : NeStack .
eq r:Stack>NeStack(X) = X .
Listing 7: Retract-Operation für den ADT Stack.
Falls die Argumentsorte von top oder pop nicht direkt als NeStack vorliegt, sondern von
einer übergeordneten Sorte ist, ersetzt OBJ das Argument bereits beim Parsen durch ein
4
Modifikationen des ADT Stack zur vorangegangenen Definition, vgl. Listing 1, werden durch Unterstreichungen hervorgehoben.
15
Kapitel 3: Erweiterte Eigenschaften von OBJ
Retract, vgl. Listing 8. Das ursprüngliche Argument wird zum Argument des Retracts. Da
empty die Gleichung des Retracts nicht erfüllt, bleibt der Ausdruck als Ergebnis der Reduktion stehen und signalisiert dadurch einen Fehler.
reduce top(empty) .
result: top(r:Stack>NeStack(empty))
Listing 8: Automatisches Einfügen der Retract-Operation.
Im folgenden Beispiel, vgl. Listing 9, wird ebenfalls ein Retract gebildet, da die Rückgabe
von pop von der Sorte Stack ist. Während der Termersetzung wird das Argument des Retracts zu einem Term von Typ NeStack umgeschrieben, so dass der Retract wahr wird und
im Ergebnis entfällt.
reduce top(pop(push(0, push(0, empty)))) .
=> top(r:Stack>NeStack(pop(push(0, push(0, empty)))))
=> top(r:Stack>NeStack(push(0, empty)))
=> top(push(0, empty))
result: 0
Listing 9: Auflösen der Retract-Operation.
An potentiellen Fehlerstellen fügt OBJ dank der Sortenhierarchie automatisch Retracts ein.
Falls diese sich im Verlauf der Termersetzung nicht auflösen, zeigen sie im Ergebnis an, an
welcher Stelle ein Fehler aufgetreten ist.
3.3
Generizität
Aus Gründen der Abstraktion und Wiederverwendung ist es wünschenswert, Spezifikationen
zu parametrisieren. Beispielsweise ist das Verhalten eines Stacks nicht davon abhängig, von
welchem Typ die Objekte sind. Anstatt mehrere Spezifikationen für verschiedene Sorten,
z. B. NAT oder INT, zu erstellen, wird ein generischer Stack definiert. Dieser Ansatz ist unter dem Begriff Generizität bekannt.5 Dadurch reduzieren sich Spezifikationsaufwand und
Fehleranfälligkeit. Bei der Spezifikation eines konkreten Systems werden oft mehrere generische Module jeweils mit OBJ-Objekten als konkretem Parameter instanziert [GWM+ 00,
S. 33].
Ein Modul wird bei der Definition wie folgt parametrisiert: obj MODUL[X :: ADD]. Hierbei ist ADD eine OBJ-Theorie (vgl. Kapitel 2.3). Im Gegensatz zu OBJ-Objekten, die abgesehen vom Isomorphismus genau eine initiale Algebra haben, hat eine Theorie die Varietät
5
In C++ findet sich dieses Konzept unter dem Namen der Templates; in Java gibt es seit der Version J2SE 5.0
Generics.
16
Kapitel 3: Erweiterte Eigenschaften von OBJ
von Algebren. Im Kontext der Parametrisierung legt eine Theorie daher die Schnittstelle des
Parameters fest. Die Algebra der Theorie muss dann durch ein OBJ-Modul erfüllt werden.
In Listing 10 legt die Theorie ADD fest, dass die OBJ-Objekte eine Additions-Operation definiert haben müssen, um die Theorie zu erfüllen.
th ADD is
sort Elt .
op _+_ : Elt Elt -> Elt .
endth
Listing 10: Theorie als Schnittstellendefinition.
Der generische Stack wird mit dem Parameter der Theorie ADD parametrisiert, vgl. Listing
11. Bei der Definition des Stacks können die Sorten und Operationen von ADD benutzt werden.
obj STACK[X :: ADD] is
sorts Stack NeStack .
subsort NeStack < Stack .
op empty : -> Stack .
op push : Elt Stack -> NeStack .
op top : NeStack -> Elt .
op pop : NeStack -> Stack .
var X : Elt . var S : Stack .
eq top(push(X, S)) = X .
eq pop(push(X, S)) = S .
endo
Listing 11: Generischer Stack.
Ein OBJ-Objekt kann eine Theorie in mehreren Weisen erfüllen. Mittels eines Views werden
deshalb die Sorten und Operationen von Theorie und Objekt eindeutig einander zugeordnet.
OBJ generiert automatisch einen Standard-View, indem es versucht, die in den Modulen zuerst definierten Sorten gleichzusetzen, vgl. Listing 12. In den meisten Fällen entfällt deshalb
die Definition eines Views.
view NAT2ADD from ADD TO NAT is
sort Elt to Nat .
op _+_ to _+_ .
endv.
Listing 12: Zuordnung zwischen Theorie und Objekt durch einen View.
Bei der Instanzierung, z. B. protecting STACK[NAT], wird dem Modul ein bzgl. der
Theorie passendes OBJ-Objekt als Parameter übergeben. Der Standard-View wird dann implizit benutzt [GWM+ 00, S. 40 f.].
17
Kapitel 3: Erweiterte Eigenschaften von OBJ
3.4
Funktionales Prototyping
OBJ ist gemäß ihren Autoren nicht nur eine Spezifikationssprache, sondern dank ihrer operationalen Semantik eine funktionale Programmiersprache. Sie umfasst ein Typsystem sowie Operationen, deren Funktionalität durch Gleichungen definiert ist. OBJ kann daher zur
Entwicklung von Prototypen eingesetzt werden. Die Autoren stellen die Hypothese auf,
dass kleine bis mittlere Systeme dank moderner Hardware ganz ohne Implementation in
einer anderen Programmiersprache auskommen [GM00]. Dieser Frage soll durch Messung
der Ausführungsgeschwindigkeit einer OBJ-Spezifikation im Vergleich zur Ausführung in
funktionalen Sprachen nachgegangen werden. Evaluiert werden OBJ3 und BOBJ als OBJImplementationen sowie Haskell und Lisp als funktionale Programmiersprachen.
Wie in Kapitel 2.5.1 beschrieben, spielt Rekursion beim Aufstellen der Gleichungen in einer OBJ-Spezifikation eine große Rolle. Als Testkandidat wird daher die binär-rekursive
Fibonacci-Funktion nach [Sed03, S. 219] evaluiert, vgl. Listing 13.
obj FIB is
protecting INT .
op fib : Int -> Int .
vars M N : Int .
eq fib(0) = 0 . eq fib(1) = 1 .
cq fib(N) = fib(N - 1) + fib(N - 2) if N >= 2 .
endo
Listing 13: Rekursive Fibonacci-Funktion als OBJ-Spezifikation.
Die Ergebnisse der Messung sind in Abbildung 4 dargestellt. Die Ausführungsgeschwindigkeit
beider OBJ-Implementationen ist erwartungsgemäß langsamer als die von Lisp und Haskell.
Das in Java implementierte BOBJ schneidet im Vergleich zu dem in Lisp entwickelten OBJ3
schlechter ab.
Abbildung 4: Laufzeit der Fibonacci-Funktion.
18
Kapitel 3: Erweiterte Eigenschaften von OBJ
Durch eine zweite Messung von Kennzahlen des Termersetzungssystems soll der durch die
Termersetzung generierte Aufwand eingeschätzt werden. Aus Abbildung 5 lässt sich entnehmen, wie viele Übereinstimmungsversuche der linken Gleichungsseite durchgeführt wurden
und wie hoch die Anzahl der Termersetzungen war. Der durch Rekursion verursachte Aufwand für das Termersetzungssystem liegt z. B. für f ib(25) bei bereits über einer Million
Termersetzungen. Die exponentielle Zuwachsrate beider Kennzahlen entspricht exakt dem
√
theoretischen Ergebnis der Aufwandsabschätzung O(n) = ( 5 + 1)n für die FibonacciFunktion [Sed03, S. 219].
Abbildung 5: Kennzahlen des Termersetzungssystems von OBJ3.
Aus den Ergebnissen lässt sich ein erster Eindruck über den Aufwand und die benötigte
Laufzeit einer OBJ-Spezifikation gewinnen. Die Ergebnisse legen die Vermutung nahe, dass
die Ausführungsgeschwindigkeit für Prototypen brauchbar ist. Eine tiefergehende Untersuchung setzt die Betrachtung weiterer Beispiele voraus, die hier jedoch über den Rahmen der
Arbeit hinausgeht.
3.5
Theorem Proving
Unter dem Begriff Theorem Proving wird im Rahmen der formalen Methoden eine Technik
verstanden, mit der Behauptungen bezüglich einer Spezifikation mithilfe eines Programms
verifiziert werden. Wie in Kapitel 2 ausgeführt wurde, basiert OBJ auf einem mathematischen Modell mit Gleichungslogik als zentralem Baustein. Auf der Basis eines Axiomensystems in Gleichungsform beweist die Berechnung eines Terms durch Termersetzung eine
Behauptung. [GWM+ 00, S. 67].
Die Beweisführung erfolgt in OBJ nicht automatisiert, sie muss manuell gewählt werden.
Beweise verlaufen in OBJ häufig nach dem aus der Mathematik bekannten Schema der Induktion. Nach [GM96, S. 195] kann dieses Verfahren auf Modelle angewendet werden, die
eine initiale Algebra besitzen. Dies bedeutet, dass die Induktion bei OBJ-Objekten, wie hier
19
Kapitel 3: Erweiterte Eigenschaften von OBJ
z. B. dem ADT Stack oder dem ADT Natürliche Zahlen, anwendbar ist. Im Kontext der Spezifikation ist dessen Grundprinzip, dass eine Eigenschaft P für alle Terme wahr ist, wenn P
für alle Operationen σ der Signatur Σ wahr ist [GM96, S. 196]. Um dies zu zeigen, wird im
Induktionsanfang bewiesen, dass die Behauptung für einen konstanten Term gilt. Für den Induktionsschritt wird das Ergebnis aus dem Induktionsanfang mitbenutzt, um dann zu zeigen,
das die Behauptung allgemein für die Anwendung der Operationen auf den konstanten Term
gilt.
Die Spezifikation eines Taschenrechners in Listing 14 macht Gebrauch von den in dieser
Arbeit vorgestellten Möglichkeiten zur Spezifikation mit OBJ. Es umfasst Polymorphismus
der Operationen, partiell geordnete Sorten zwecks Fehlerbehandlung sowie Strukturierung
durch Modularisierung und Parametrisierung. Bezüglich dieser Spezifikation soll ein Beweis
nach dem oben beschrieben Muster der Induktion vorgestellt werden. Der Taschenrechner
funktioniert nach dem Prinzip, dass zuerst zwei Zahlen eingegeben werden müssen (Operation enter), die anschließend addiert werden (Operation add). Durch den Mechanismus der
Retracts wird zugesichert, dass eine Addition nur dann ausgeführt wird, wenn bereits zwei
Zahlen eingegeben wurden. Die Untersorten State1 und State2 repräsentieren daher die
Anzahl der bereits eingegebenen Zahlen.
obj CALC[X :: ADD] is
sort State State1 State2 .
subsorts State2 < State1 < State .
op init : -> State .
*** Signatur
op save : Elt State -> State1 .
op save : Elt State1 -> State2 .
op enter : Elt State1 -> State2 .
op enter : Elt -> State1 .
op show : State1 -> Elt .
op add : State2 -> State1 .
vars I I1 I2 : Elt .
*** Axiome
var S : State .
eq enter(I) = save(I, init) .
eq enter(I, S) = save(I, S) .
eq show(save(I, S)) = I .
eq add(save(I1, save(I2, S))) = save(I1 + I2, S) .
endo
Listing 14: OBJ-Modul eines Taschenrechners.
Die Induktion in Listing 15 beweist die Kommutativität der Addition des Taschenrechners.
Es wird also gezeigt, dass die Reihenfolge der Eingaben der Zahlen keine Rolle spielt. In OBJ
reicht es zur Beweisführung nicht aus, Terme durch das Termersetzungssystem berechnen zu
20
Kapitel 3: Erweiterte Eigenschaften von OBJ
lassen. Das Resultat der Berechnungen muss überprüft werden.
Im folgenden Beispiel ist der Beweis durch Induktion nur dann gültig, wenn beide Termersetzungen zum Ergebnis true führen. Die Beweisführung fordert einen starken Eingriff des
Spezifizierers: Würde der Induktionsanfang nicht zu true ausgewertet, dann wird die Behauptung dennoch in Form der darauf folgenden Gleichung zur Menge der Axiome hinzugefügt. In OBJ gibt es daher einen speziellen Kommentarstil, ***> should be: true,
der bei der Termersetzung darauf hinweisen soll, welches Ergebnis erwartet wurde.
*** Laden von Modulen
in addable
in calc
in natpeano
*** Instanzierung
obj CALCNAT
is protecting CALC[NAT] .
endo
*** Beweis per Induktion
open CALCNAT .
op M : -> Nat .
op N : -> Nat .
*** Induktionsanfang
reduce add(enter(M, enter(0))) == add(enter(0, enter(M))) .
***> should be: true
*** Gleichung hinzufügen
eq add(enter(M, enter(N))) = add(enter(N, enter(M))) .
*** Induktionsschritt
reduce add(enter(M, enter(s(N)))) == add(enter(s(N), enter(M))) .
***> should be: true
close
Listing 15: Beweisführung der Kommutativität für das OBJ-Modul des Taschenrechners.
21
Kapitel 4: Zusammenfassung und Ausblick
4
Zusammenfassung und Ausblick
Der Einsatz von OBJ als Spezifikationssprache setzt Kenntnisse in der Begriffswelt der algebraischen Spezifikation voraus. Auf der Basis einer Signatur legen Terme und Gleichungen
die Semantik einer Spezifikation fest. Dank des zugrunde liegenden Gleichungsmodells werden Spezifikationen zu Termersetzungssystemen, mit deren Hilfe das Ergebnis von Termen
berechnet wird. Diese Berechenbarkeit erlaubt schließlich den formalen Beweis von Eigenschaften des spezifizierten Systems. An dieser Stelle ist unter Rückgriff auf bekannte Vorgehensweisen, wie z. B. die Induktion, Kreativität zur Beweisführung gefordert. Weiterhin ist
der Spezifizierer bei der Ausführung von Beweisen weitgehend auf sich gestellt, OBJ bietet
hier kaum Unterstützung.
Zur Strukturierung von Spezifikationen steht in OBJ ein vielseitiges Modulkonzept bereit.
Zusammen mit der Möglichkeit Module zu parametrisieren, ist OBJ eine Spezifikationssprache, mit der auch komplexeren Problemstellungen entgegengetreten werden kann. Denn
es können bereits in dieser frühen Phase des Software-Entwicklungsprozesses Probleme in
Teilprobleme aufgeteilt werden. Falls eine später gewählte Programmiersprache, wie z. B.
Java, diese Konzepte der Modularisierung ebenfalls unterstützt, kann eine Implementierung
nahe auf der Spezifikation aufbauen. Die Orientierung an Datentypen und die oft rekursive
Art der Gleichungsdefinition bringen eine starke Nähe zu Programmiersprachen mit sich.
Bei der Aufstellung der Gleichungen trifft der Spezifizierer auf Konzepte, die aus der funktionalen sowie regelbasierten Programmierung bekannt sind. Daher kann er Gefahr laufen,
eher ein Programm anstelle einer Spezifikation zu schreiben.
Die Beschäftigung mit OBJ hat gezeigt, dass es oft nicht leicht ist, sich von einem niedrigen Abstraktionsniveau zu lösen. Dieser Eindruck wird durch Beispiele in der Literatur unterstützt. Dort finden sich u. a. die Spezifikation eines Protokolls zur Datenübertragung oder
die Spezifikation einer imperativen Programmiersprache. Neuere Ansätze der verhaltensorientierten Spezifikation, die auf der algebraischen Spezifikation aufsetzt, entgegnen dieser
Limitierung. Mittels Hidden-Sorts werden Module durch Zustände, Attribute und Methoden
erweitert, wie sie aus der Objektorientierung bekannt sind. Darauf aufbauend lässt sich das
Abstraktionsniveau bei der Spezifikation weiter erhöhen.
Bezüglich des ökonomisch sinnvollen Einsatzes von OBJ ist zu sagen, dass die formale Spezifikation grundsätzlich einen erhöhten Aufwand mit sich bringt. Ihr Einsatz sollte deshalb
auf Teilprobleme beschränkt werden. Geeignete Kriterien zu Auswahl stellen z. B. Sicherheit
oder entstehende Kosten im Fehlerfall dar. Im Hinblick auf die endgültige Software darf genau wie bei anderen formalen Methoden nicht vergessen werden, dass durch Verifikation nur
Eigenschaften des Programms bezüglich seiner Spezifikation bewiesen werden können. Ob
Spezifikation und Programm eine Lösung auf die Problemstellung bieten, muss im Rahmen
der Validation gesondert geklärt werden.
22
Kapitel A: Software
A
Software
A.1
Übersicht
Software
BOBJ
OBJ3
Haskell (Hugs)
Lisp (GNU Common Lisp)
Version
0.9
2.09
Nov 2003
2.6.6 CLtL1
Download
http://www.cs.ucsd.edu/groups/tatami/bobj
http://secure.ucd.ie/products/opensource/OBJ3/
http://haskell.org/hugs
http://www.gnu.org/software/gcl/gcl.html
Tabelle 1: Verwendete Software.
A.2
Installation von OBJ3
OBJ3 ist die neueste Version einer Serie von OBJ-Systemen, die auf die erste Entwicklung
von Joseph Goguen 1976 zurückgehen. Das Kommandozeilen-System ist in Common Lisp
entwickelt worden. Unter Linux, hier Debian GNU Linux, wird eine ausführbare Version aus
den Quellen erstellt.
Unter Debian GNU Linux 3.1 sind weiterhin folgende Pakete nötig (durch Abhängigkeiten
zu diesen Pakete werden weitere Pakete installiert):
• gcl (GNU Common Lisp compiler)
• g++ (The GNU C++ compiler)
• make (The GNU version of the make“ utility)
”
• emacs21-bin-common (The GNU Emacs editor’s shared, architecture)
• flex-old (The old version of the fast lexical analyzer)
• bison (A parser generator that is compatible with Y)
Nach dem Extrahieren der Quellen wird der Übersetzungsvorgang durch make sources
angestoßen. Der OBJ3-Prompt kann anschließend durch bin/obj3-gcl gestartet werden.
Damit nach reduce angezeigt wird, wie lange die Reduktion gedauert hat, muss die Variable
$$time-red im Sourcecode von OBJ3 in source/obj3/top/ci.lsp auf den boolschen
Wert t (true) gesetzt werden.
23
Kapitel A: Software
A.3
Installation von BOBJ
BOBJ gehört zur den neueren Entwicklungen von OBJ-Systemen. Auch hier ist Joseph Goguen als einer der Autoren zu nennen. BOBJ basiert auf den von OBJ3 bekannten Methoden
zur algebraischen Spezifikation und Verifikation. Darüber hinaus werden Möglichkeiten zur
verhaltensorientierten Spezifikation und Verifikation zur Verfügung gestellt. Abgesehen von
Erweiterungen ist die Syntax von BOBJ nahezu identisch zu der von OBJ3.
Da BOBJ vollständig in Java realisiert ist, ist es auf jeder Plattform lauffähig, auf der die
Java 2 Runtime installiert ist (Download unter http://java.sun.com). Der BOBJ-Prompt wird
durch java -cp bobj.jar gestartet.
24
Kapitel B: Funktionales Prototyping
B
B.1
Funktionales Prototyping
Benchmark-System
Betriebssystem
Prozessor
Arbeitsspeicher
Debian GNU Linux 3.1
AMD Athlon XP 3000+
1 GB
Tabelle 2: Eigenschaften des Benchmark-Systems.
B.2
Quelltext Benchmark-Module
module Bench(bench) where
import CPUTime
bench f = do
time0 <- getCPUTime
putStr $ "Result: " ++ f ++ "\n"
time1 <- getCPUTime
putStr $ (show $ (fromIntegral (time1 - time0) / (10ˆ12)))
putStr $ " s\n"
Listing 16: Benchmark-Funktionen in Haskell.
(defun bench (f) (progn
(setq time0 (get-internal-run-time))
(setq res (funcall f))
(setq time1 (get-internal-run-time))
(format t "Result: ˜d˜%" res)
(format t "Run time: ˜8,4f˜%" (/ (float (- time1 time0))
internal-time-units-per-second))
))
Listing 17: Benchmark-Funktionen in Lisp.
B.3
Quelltext Fibonacci-Funktion
import Bench
fib :: Integer -> Integer
fib 0 = 1
fib 1 = 1
25
Kapitel B: Funktionales Prototyping
fib n = fib(n-2) + fib(n-1)
bench_fib n = bench (show (fib n))
-- bench_fib 20
Listing 18: Rekursive Fibonacci-Funktion in OBJ.
import Bench
fib :: Integer -> Integer
fib 0 = 1
fib 1 = 1
fib n = fib(n-2) + fib(n-1)
bench_fib n = bench (show (fib n))
-- bench_fib 20
Listing 19: Rekursive Fibonacci-Funktion in Haskell.
(defun fib (n)
(if (or (= n 0) (= n 1)) 1
(+ (fib (- n 1)) (fib (- n 2)))
)
)
(load "Bench.lsp")
(defun bench_fib (n) (bench (lambda () (fib n))))
; (bench_fib 10)
Listing 20: Rekursive Fibonacci-Funktion in Lisp.
26
Literaturverzeichnis
Literatur
[EGL89] E HRICH, H. D. ; G OGOLLA, M. ; L IPECK, U.: Algebraische Spezifikation
abstrakter Datentypen. Stuttgart : Teubner, 1989
[GM92] G OGUEN, J. A. ; M ESEGUER, J.: Order-sorted algebra I: equational deduction
for multiple inheritance, overloading, exceptions and partial operations. In:
Theor. Comput. Sci. 105 (1992), Nr. 2, S. 217–273. – ISSN 0304–3975
[GM96] G OGUEN, J. A. ; M ALCOLM, G.: Algebraic Semantics of Imperative Programs. Boston, 1996
[GM00] G OGUEN, J. A. ; M ALCOLM, G.: Introduction. In: G OGUEN, J. A. (Hrsg.) ;
M ALCOLM, G. (Hrsg.): Software Engineering with OBJ: Algebraic Specification in Action. Boston, 2000
[GWM+ 00] G OGUEN, J. A. ; W INKLER, T. ; M ESEGUER, J. ; F UTATSUGI, K. ; J OUAN NAUD , J.P.: Introducing OBJ. In: G OGUEN , J. A. (Hrsg.) ; M ALCOLM , G.
(Hrsg.): Software Engineering with OBJ: Algebraic Specification in Action. Boston, 2000
[HL89] H ORBEEK, I.V. ; L EWI, J.: Algebraic Specifications in Software Engineering. Springer Verlag, 1989
[LEW96] L OECKX, J. ; E HRICH, H. D. ; W OLF, M.: Specification of Abstract Data
Types. Wiley, 1996
[Sed03] S EDGEWICK, R.: Algorithms in Java. Addison-Wesley, 2003
27
Zugehörige Unterlagen
Herunterladen