ASF^{+} — eine ASF-ähnliche Spezifikationssprache

Rüdiger Lunde, Claus-Peter Wirth

December 22, 1994

SEKI-WORKING-PAPER SWP–94–05 (SFB)

Fachbereich Informatik, Universität Kaiserslautern, D–67663 Kaiserslautern

Zusammenfassung: Ohne auf wesentliche Aspekte der in [Bergstra&al.89] vorgestellten algebraischen Spezifikationssprache ASF zu verzichten, haben wir ASF um die folgenden Konzepte erweitert: Während in ASF einmal exportierte Namen bis zur Spitze der Modulhierarchie sichtbar bleiben müssen, ermöglicht ASF^{+} ein differenziertes Verdecken von Signaturnamen. Das fehlerhafte Vermischen unterschiedlicher Strukturen, welches in ASF beim Import verschiedener Aktualisierungen desselben parametrisierten Moduls auftritt, wird in ASF^{+} durch eine adäquatere Form der Parameterbindung vermieden. Das neue Namensraum-Konzept von ASF^{+} erlaubt es dem Spezifizierer, einerseits die Herkunft verdeckter Namen direkt zu identifizieren und anderseits beim Import eines Moduls auszudrücken, ob dieses Modul nur benutzt oder in seinen wesentlichen Eigenschaften verändert werden soll. Im ersten Fall kann er auf eine einzige global zur Verfügung stehende Version zugreifen; im zweiten Fall muß er eine Kopie des Moduls importieren. Schließlich erlaubt ASF^{+} semantische Bedingungen an Parameter und die Angabe von Beweiszielen.

Diese Arbeit ist aus einer von Klaus Madlener und Claus-Peter Wirth betreuten Projektarbeit Rüdiger Lundes hervorgegangen und wurde gefördert von der Deutschen Forschungsgemeinschaft, SFB 314 (D4-Projekt).

Abstract: Maintaining the main aspects of the algebraic specification language ASF as presented in [Bergstra&al.89] we have extend ASF with the following concepts: While once exported names in ASF must stay visible up to the top the module hierarchy, ASF^{+} permits a more sophisticated hiding of signature names. The erroneous merging of distinct structures that occurs when importing different actualizations of the same parameterized module in ASF is avoided in ASF^{+} by a more adequate form of parameter binding. The new “Namensraum”-concept of ASF^{+} permits the specifier on the one hand directly to identify the origin of hidden names and on the other to decide whether an imported module is only to be accessed or whether an important property of it is to be modified. In the first case he can access one single globally provided version; in the second he has to import a copy of the module. Finally ASF^{+} permits semantic conditions on parameters and the specification of tasks for a theorem prover.

Inhaltsverzeichnis

1 Einleitung 1

2 Das Konzept, erklärt anhand von Beispielspezifikationen 2 2.1 Bottom-Up-Spezifikationen . . . . . . . . . . . . . . . . . . . . . . . . . 2 2.2 Parametrisierte Module . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.3 Das Namensraumkonzept . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.4 Explizites Renaming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.5 Parameterbindungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

3 Strukturdiagramme 13

4 Semantik hierarchischer Konzepte 16 4.1 Der “benutzende” Import . . . . . . . . . . . . . . . . . . . . . . . . . . 16 4.2 Der “kopierende” Import . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 4.3 Abhängigkeiten zwischen Namensräumen . . . . . . . . . . . . . . . . . . 24 4.4 Verdeckte Namen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 4.5 Overloading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

5 Syntax 28

6 Die Normalform-Prozedur 30 6.1 Datenstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 6.2 Der Algorithmus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37

6.2.1 Globale Hilfsfunktionen für Sichtbarkeitsänderungen . . . . . . . . . . 37 6.2.2 Kombination von Modulen . . . . . . . . . . . . . . . . . . . . . . . . 39 6.2.3 Modulmodifikationen in Importbefehlen . . . . . . . . . . . . . . . . 44 6.2.4 Die Normalisierungsfunktionen NF und NormalForm . . . . . . . . . 51

6.3 Ein Beispiel für ein normalisiertes Modul . . . . . . . . . . . . . . . . . . 54

7 Abschließende Zusammenfassung 57

Literatur 58

1 Einleitung

Mit steigender Leistungsfähigkeit moderner automatischer Beweissysteme wächst auch die Komplexität der mit ihnen zu bearbeitenden Problemstellungen. Auf der Suche nach Konzepten zur logisch strukturierten Formulierung derartiger Probleme haben sich in der Entwicklung von Spezifikationssprachen Modularisierungsansätze herausgebildet. Eine Spezifikation besteht danach aus mehreren Modulen, die mit Hilfe von Importbefehlen aufeinander Bezug nehmen. Besonders in umfangreichen Spezifikationen erweisen sich modulare Repräsentationen von Spezifikationen als vorteilhaft. Die Verständlichkeit wird durch die Zerlegung in einzelne, durch exakt definierte Schnittstellen (Importkonstrukte) miteinander verbundene Teilspezifikationen gesteigert. Außerdem können häufig verwendete Strukturen (beispielsweise die Datenstruktur Boolean) in Bibliotheken abgelegt werden, was den Spezifikationsaufwand reduziert.

Verschiedene Möglichkeiten, Module miteinander zu kombinieren, werden in dieser Arbeit diskutiert. Das Hauptinteresse gilt der Entwicklung einer Sprache für modulare Spezifikationen mit positiv/negativ bedingten Gleichungen. Ausgehend von der in [Bergstra&al.89] vorgestellten Sprache ASF, die bereits über ein recht differenziertes Modularisierungskonzept verfügt, wird eine Erweiterung ASF^{+} vorgestellt, welche die im ersten und vorvorletzten Punkt von “1.4.1. Known defects and limitations of ASF” in [Bergstra&al.89] genannten Mängel von ASF behebt. ASF^{+} unterstützt:

• Import und Parametrisierung von Modulen

• Überladen von Funktionsnamen

• Infix-Operatoren

• differenziertes Verdecken von Funktions- und Sortennamen

• positiv/negativ bedingte Gleichungen

• rudimentäre Verwaltung von Beweiszielen

Als Semantik wird, analog zu [Bergstra & al. 89], semi-formal eine Normalisierungsprozedur angegeben, welche die Modulhierachie einer komplexen Spezifikation in eine flache Spezifikation (ohne Importe) umwandelt. Von zentraler Bedeutung ist in diesem Zusammenhang die Originfunktion, die jedem in der Spezifikation auftretenden Namen einen Informationsblock zuweist. Dieser enthält für den Normalisierungsprozeß wichtige Informationen über den Kontext der Namensdefinition, beispielsweise den Namen des Definitionsmoduls. Neben der Originfunktion verwaltet die Normalisierungsprozedur aus ASF^{+} eine Dependenzfunktion. Sie spielt bei expliziten Umbenennungen und Parameterbindungen eine wichtige Rolle und trägt der hierarischen Struktur der Spezifikation Rechnung. Neu in ASF^{+} ist auch, daß bei der Kombination von Modulen das Umbenennen von verdeckten Namen nicht ausschließlich durch Konfliktfreiheit definiert wird. Jeder verdeckte Name beinhaltet in ASF^{+} unter anderem das Kürzel des Herkunftsmoduls, was zum einen Konfliktfreiheit garantiert, zum andern auch modulare Information sichtbar macht und damit der Übersicht dient.

2 Das Konzept, erklärt anhand von Beispielspezifikationen

Um mit der Syntax von ASF^{+} vertraut zu werden und ein erstes intuitives Verständnis der neuen Sprache zu gewinnen, bietet es sich an, zunächst einige Beispielspezifikationen zu betrachten. Die hier angegebenen Module Booleans, Naturals und Sequences entsprechen im wesentlichen den gleichnamigen Modulen aus [Bergstra & al. 89], Kapitel 1.1.2., was einen direkten Vergleich erlaubt.

2.1 Bottom-Up-Spezifikationen

module Booleans
short  Bo
{
   add signature
   {  public:
         sorts
            BOOL
         constructors
            true, false :             -> BOOL
         non-constructors
            and, or     : BOOL # BOOL -> BOOL
      private:
         non-constructors
            not         : BOOL        -> BOOL  }
   variables
   {  non-constructors
         x,y : -> BOOL  }
   equations
   {
      macro-equation and(x,y)
      {
         case
         {  ( x @ true ) : y
            ( x @ false ): false  }
      }
      macro-equation not(x)
      {
         case
         {  ( x @ true ) : false
            ( x @ false ): true  }
      }

      [e1] or(x, y) = not(and(not(x), not(y)))
   }
} /* Booleans */

Jedes Modul einer Spezifikation beginnt mit dem Schlüsselwort module, gefolgt vom Modulnamen, dem optionalen short-Konstrukt und einem Block. Das short-Konstrukt stellt ein Modulnamenkürzel zur Verfügung, das beim Umbenennen verdeckter Namen Verwendung findet und zumindest bei langen Modulnamen nicht fehlen sollte. Fehlt die Angabe des Modulkürzels, so wird der Modulname selbst ersatzweise als sein eigenes Kürzel verwendet. Die Kürzel werden global zur Bezeichnung der Module herangezogen und müssen daher innerhalb der Spezifikation eindeutig sein.

Alle nicht importierten Teile der Signatur werden mit dem add signature-Konstrukt zur internen Signatur zusammengefaßt. Sie umfaßt einen nach außen sichtbaren (public) und einen nur innerhalb des Moduls zugänglichen (private) Bereich, in denen Sorten- und Funktionsnamen deklariert werden können. Da der Spezifikationssemantik ein konstruktorbasierter Ansatz zu Grunde liegt (vergleiche etwa [Wirth&Gramlich93] oder [Wirth&Gramlich94]), wird zwischen constructors und non-constructors unterschieden. Im Beispiel sind die Sorte BOOL, die Konstanten true, false und die (Prädikats-) Funktionen and, or nach außen sichtbar (können also von anderen Modulen importiert werden). not wird zu Illustrationszwecken nicht exportiert, und kann infolgedessen nur innerhalb des Moduls referenziert werden.

Im Beispiel folgt eine Variablenvereinbarung, die jeder im Gleichungsblock verwendeten Variable eine Sorte zuweist. Die Overloadingfähigkeit von ASF^{+} (d.h. die Möglichkeit namensgleiche Funktionen mit verschiedenen Argumentsorten zu unterscheiden) macht eine Deklaration aller Variablen zwingend notwendig. ASF^{+} unterscheidet zwischen Konstruktor- und Non-Konstruktor-Variablen, die durch die Schlüsselwörter constructors und non-constructors gekennzeichnet werden. Defaultwert ist constructors. Werden nur Konstruktor-Variablen verwendet, so kann deshalb das Schlüsselwort (wie in den folgenden Beispielen) entfallen.

ASF^{+} unterstützt Spezifikationen mit positiv/negativ bedingten Gleichungen. Sie können im Gleichungsblock entweder explizit angegeben werden (im Beispiel die Zeile mit Marke e1) oder mit Hilfe des macro-equation-Konstrukts erzeugt werden. Das macro-equation-Konstrukt geht aus dem macro-rule-Konstrukt aus [Wirth&Lunde94] hervor und unterscheidet sich nur durch die C-ähnliche Syntax. Seine Semantik ist durch Makro-Expansion in positiv/negativ bedingte Gleichungen gegeben. Eine wichtige Rolle spielen sogenannte match-conditions (Symbolisiert durch @), mit deren Hilfe Gleichungen, deren linke Seiten mit dem gleichen Funktionssymbol beginnen, zusammengefaßt werden können. Im Beispiel führt die Makro-Expansion zu den vier Gleichungen

[me-and1] and(true, y)  = y
[me-and2] and(false, y) = false
[me-not1] not(true)     = false
[me-not2] not(false)    = true

Bei umfangreichen Funktionsdefinitionen bietet die Darstellung als macro-equation große Vorteile, weil durch verschachtelte case-Konstrukte zahlreiche Wiederholungen von Bedingungen eingespart werden können. Für die genaue Bedeutung der Makros @, case, if und else sei auf [Wirth&Lunde94] verwiesen.

Alle verwendeten Variablen und Marken werden semantisch wie private-deklarierte Signaturnamen behandelt und müssen nur innerhalb des Moduls eindeutig sein.

module Naturals
short  Nat
{
   import Booleans {  public: BOOL, true, false  }
   add signature
   {
      public:
         sorts
            NAT
         constructors
            0     :           -> NAT
            s     : NAT       -> NAT
         non-constructors
            _+ _: NAT # NAT -> NAT
            eq    : NAT # NAT -> BOOL
   }
   variables
   {  x,y,u : -> NAT  }
   equations
   {
      macro-equation (x + y)
      {
         case
         {  ( y @ 0 )    : x
            ( y @ s(u) ) : s(x + u)  }
      }
      macro-equation eq(x,y)
      {  if ( x = y ) true
            else      false  }
   }
} /* Naturals */

Das Modul Naturals importiert das Modul Booleans. Der Block, der dem Importbefehl folgt, trägt der Forderung nach einem flexiblen Lokalitätsprinzip Rechnung. Er sorgt dafür, daß nur die im Block aufgeführten Namen im Modul zugänglich sind. Im Beispiel sind die Sorte BOOL und die Konstanten true und false innerhalb des Moduls Naturals sichtbar und werden auch von ihm exportiert. Die von Booleans exportierten, aber im Importkonstrukt nicht aufgeführten Funktionen and und or und die nicht exportierte Funktion not können innerhalb von Naturals nicht referenziert werden. Ihre Namen gelten als verdeckt (hidden).

Unter den im add signature-Konstrukt deklarierten Funktionssymbolen befindet sich auch der Infix-Operator “+”. Seine Deklarationssyntax wurde, wie auch die der Präfix-Operatoren, aus ASF übernommen.

module OrdNaturals
short  ONat
{
import Booleans
{  public: BOOL, true; private: or }
import Naturals
{  public: NAT, 0, s, eq, false  }
add signature
{  public:
      non-constructors
         greater, geq: NAT # NAT -> BOOL  }
variables
{  x,y,u,v : -> NAT  }
equations
{
   macro-equation greater(x,y)
   {
      case
      {  ( x @ 0 )              : false
         ( x @ s(u), y @ 0 )    : true
         ( x @ s(u), y @ s(v) ) : greater(u,v)  }
   }
   [e1] geq(x,y) = or(greater(x,y), eq(x,y))
}
goals
{  [irref] greater(x, x)
      -->
   [trans] greater(x, u), greater(u, y)
      -->  greater(x, y)
   [total]
      -->  greater(x, y), greater(y, x), x = y  }
} /* OrdNaturals */

OrdNaturals spezifiziert eine irreflexive Ordnung greater und eine reflexive Ordnung geq für Elemente des Typs NAT. Der doppelte Import des Moduls Booleans (direkt und indirekt über Naturals) demonstriert, daß die Sichtbarkeit von Namen eines importierten Moduls im allgemeinen nicht von einem Importbefehl allein abhängt. So wäre es falsch, aus dem Fehlen des Namens false im ersten Importblock abzuleiten, daß false innerhalb von OrdNaturals verdeckt sein muß.

Der goals-Block am Ende von OrdNaturals ermöglicht es dem Spezifizierer, Beweisziele anzugeben. Jede Beweisaufgabe besteht aus einer in eckigen Klammern eingefaßten Marke, gefolgt von einer Gentzenklausel. Syntaktisch handelt es sich dabei um eine Folge von durch Kommas getrennte Gleichungen, gefolgt von einem Pfeil und einer weiteren Folge von Gleichungen. Semantisch ist die Gentzenformel e_{1}, …, e_{n} --> e_{n+1}, …, e_{n+m} equivalent zu e_{1} ∧ … ∧ e_{n} ⟶ e_{n+1} ∨ … ∨ e_{n+m}. Gleichungen der Form P (x_{1}, …, x_{n}) = true können wie im Beispiel durch P (x_{1}, …, x_{n}) abgekürzt werden. Syntaktisch korrekt ist eine solche abgekürzte Gleichung jedoch nur dann, wenn true innerhalb des Moduls sichtbar und sortengleich mit der Zielsorte von P ist. In ASF^{+} werden alle Beweisziele exportiert. Auf Flags zur Beschränkung der Sichtbarkeit, wie sie in ART [Eschbach94] Verwendung finden, wird verzichtet. ASF^{+} versteht sich als Eingabeschnittstelle zu einem Beweiser, nicht als Ausgabeschnittstelle. Deshalb wird auch auf solche Flags verzichtet, die Auskunft darüber geben, welche der Klauseln als bewiesen gelten dürfen und welche nicht. Der Stempel “proved” ohne einen Verweis auf den Beweis, ist ohnehin von zweifelhaftem Wert, zumal kaum überprüft werden kann, ob die Spezifikation nach setzen des Flags vom Benutzer verändert wurde. Es wird davon ausgegangen, daß der Beweiser für die bearbeitete Spezifikation eine Datei anlegt, die Informationen über die Spezifikation enthält (zum Beispiel den Namen des Top-Moduls und Datum+Zeit der letzten Spezifikationsmodifikation) und neben allen bewiesenen Theoremen auch Referenzen auf die Beweise beinhaltet.

2.2 Parametrisierte Module

Die bisher eingeführten Konstrukte erscheinen ausreichend für Bottom-Up-Spezifikationen. Wünschenswert sind jedoch auch Mechanismen, die es gestatten, Freiräume innerhalb eines Modules zu erhalten, die erst später (z. B. beim Import des Moduls in ein weiteres) mit konkretem Inhalt gefüllt werden müssen. Das Parameterkonzept von ASF^{+} gestattet es, Sorten und Funktionen in ein parametrisiertes Modul nachträglich durch Parameterbindung zu “implantieren”. Als Beispiel betrachten wir das Modul Sequences, in dem Sequenzen von nicht näher spezifizierten Elementen definiert werden. Als Konstruktoren dienen nil (erzeugt die leere Sequenz) und cons (fügt ein Element an eine Sequenz an).

module Sequences <(ITEMpar)>
short  Seq
{
   add signature
   {
      parameters:
      (  sorts
            ITEMpar  )
      public:
         sorts
            SEQ
         constructors
            nil      :               -> SEQ
            cons     : ITEMpar # SEQ -> SEQ
   }
} /* Sequences */

In ASF^{+} müssen alle formalen Parameter (ob importiert, oder wie im Beispiel im add signature-Konstrukt deklariert) an prominenter Stelle direkt hinter dem Modulnamen in spitzen Klammern angegeben werden. Beim Auftreten mehrerer Parameter kann mit Hilfe der runden Klammern die Zahl der möglichen Parameterbindungen eingeschränkt werden. Alle Parameter eines durch runde Klammern eingefaßten Tupels dürfen nur an Namen desselben Moduls gebunden werden.

Auch OrdSequences (unten) spezifiziert Sequenzen über eine durch Bindung des Parameters ITEMpar zu präzisierende Sorte von Elementen. In Frage kommen hier jedoch nur Sorten, für die eine irreflexive Ordnung spezifiziert wurde. Mit Hilfe dieser Ordnung wird eine lexikographische Ordnung auf Sequenzen definiert.

module OrdSequences <(ITEMpar, ordpar)>
short  OSeq
{
   import Booleans {public: BOOL, true, false}
   add signature
   {

parameters:
(  sorts
   ITEMpar
non-constructors
   ordpar : ITEMpar # ITEMpar -> BOOL
conditions
   [irref] ordpar(i1,i1)
      -->
   [trans] ordpar(i1,i2), ordpar(i2,i3)
      -->  ordpar(i1,i3)
   [total]
      -->  ordpar(i1,i2), ordpar(i2,i1), i1 = i2
)
public:
sorts
SEQ
constructors
nil      :               -> SEQ
cons     : ITEMpar # SEQ -> SEQ
non-constructors
greater  : SEQ     # SEQ -> BOOL
}
variables
{  i1, i2, i3         : -> ITEMpar
seq1, seq2, s1, s2 : -> SEQ  }
equations
{
macro-equation greater(seq1, seq2)
{              /* lex-order on sequences */
case
{
( seq1 @ nil )                     : false
( seq1 @ cons(i1, s1), seq2 @ nil ): true
( seq1 @ cons(i1, s1), seq2 @ cons(i2, s2) ):
   if ( ordpar(i1, i2) )
         true
      else if ( i1 = i2 )
                 greater(s1, s2)
              else
                 false
}
}
}
} /* OrdSequences */

ASF^{+} verzichtet im Gegensatz zu ASF auf die Einführung eines formalen Parameters für den Modulnamen, an den ein Parameter-Tupel gebunden wird. Als Parameter werden in ASF^{+} statt dessen die Sorten- und Funktionsnamen innerhalb der Parameter-Tupel bezeichnet. Die Gruppierung der Parameter in Blöcke (hier Tupel, dargestellt durch runde Klammern) wird jedoch beibehalten, weil sie sich bei der Formulierung semantischer Bedingungen als vorteilhaft erweist. Funktionsparameter können nicht überladen werden.

Unter “semantischen Bedingungen” verstehen wir in ASF^{+} Gentzenklauseln, die in der Definition eines Parameter-Tupels im Parameterteil des add signatur-Konstrukts angegeben werden können (hier irref, trans und total). Die Zulässigkeit der Bindung eines Parameter-Tupels an Namen eines Moduls M_{ACT} hängt nun davon ab, ob die aus der Bindung hervorgehenden Gentzenklauseln innerhalb von M_{ACT} gelten oder nicht. Da dieses Problem im allgemeinen unentscheidbar ist wird zusätzlich gefordert, daß M_{ACT} Beweisziele enthält, die sich nur durch die Marken- und Variablennamen von den Bedingungsklauselinstanzen unterscheiden und für die bereits Beweise existieren. Mit Bedingungen verknüpfte Parameter-Tupel können nur an Namen solcher Module gebunden werden, die keine ungebundenen Parameter mehr enthalten, weil für Module mit freien Parametern (bisher) keine Semantik innerhalb des ASF-Ansatzes existiert.

Mit dem Konzept der semantischen Bedingungen werden vor allem zwei Ziele verfolgt: Einerseits werden semantisch unsinnige Parameterbindungen schon in der Akzeptanzphase der Spezifikation erkannt, außerdem können diese Bedingungen in parametrisierten Beweisen als Lemmas von Bedeutung sein, weil sie die “wesentlichen” Eigenschaften der Parameter enthalten.

Beim Import eines parametrisierten Moduls sind alle Parametertupel hinter dem Modulnamen in eckigen Klammern aufzuführen:

   import OrdSequences <(ITEMpar, ordpar)>
   {  public: SEQ, nil, cons  }

Da Parameter nicht verdeckt werden können, entspricht diese Syntax dem Grundsatz, daß alle innerhalb eines Moduls sichtbaren Namen dort auch angegeben werden müssen.

2.3 Das Namensraumkonzept

Eine Grundidee des flexiblen Verdeckungsmechanismus aus ASF^{+} ist die eindeutige Zuordnung von Namen zu Namensräumen. Im wesentlichen beschreibt der Namensraum das Modul, in dem der Name zum ersten Mal in Erscheinung tritt (im Folgenden als Definitionsmodul bezeichnet). Im Beispiel Naturals gehören unter anderem NAT und 0 dem Namensraum Naturals und BOOL und true dem Namensraum Booleans an. Der Name x kommt in beiden Namensräumen als Variable in unterschiedlicher Bedeutung vor. Ein Namensraum umfaßt also alle innerhalb eines Moduls eingeführten Namen (einschließlich Marken) abzüglich der importierten. Die Namen eines Moduls gehören im allgemeinen also verschiedenen Namensräumen an. Wir bezeichnen den Namensraum, dem die im Modul definierten Namen angehören, als den moduleigenen Namensraum (er erbt auch den Namen des Moduls), alle anderen heißen importierte Namensräume. Namen aus verschiedenen Importbefehlen können nur dann miteinander identifiziert werden, wenn sie dem gleichen Namensraum angehören, was bei mehrfachem Import desselben Moduls der Fall sein kann.

Was geschieht aber, wenn Namen Namensräumen von Modulen angehören, die durch Renaming oder Parameterbindung beim Import “manipuliert” wurden? ASF^{+} löst das Problem durch Schaffung neuer Namensrauminstanzen, die Kopien der ursprünglichen Namensräume repräsentieren. Das Kopieren einzelner Namen aus ASF wird durch gruppenweises Kopieren ersetzt, deren kleinste Einheiten die Namensräume bilden. Die schwerwiegenden Gründe für diese konzeptionelle Entscheidung werden im Kapitel 4.2 diskutiert.

2.4 Explizites Renaming

Unter explizitem Renaming verstehen wir in ASF^{+} das Umbenennen von Signatur- und Parameternamen aus importierten Modulen mit Hilfe des renamed to-Konstrukts.

module Integers
short  Int
{
   import Naturals[Int1]
   {  public: NAT renamed to INT, 0, s, +, eq  }
   add signature {  public: constructors p : INT -> INT }
   variables {  x, y : -> INT  }
   equations
   {  [e1]  s(p(x))  = x
      [e2]  p(s(x))  = x
      [e3]  p(x) + y = p(x + y)  }
} /* Integers */

Integers spezifiziert den Datentyp der ganzen Zahlen unter Verwendung der natürlichen Zahlen. In ASF^{+} wird erwartet, daß jeder Importbefehl, in dem ein explizites Renaming oder eine Parameterbindung vorgenommen wird, eine innerhalb der Spezifikation eindeutige (möglichst kurze) Instanzbezeichnung (im Beispiel Int1) beinhaltet. Sie wird gebraucht, um Namen unterschiedlich instanziierter Namensräume zu unterscheiden.

Im letzten Beispiel gehören u. a. INT, 0 und s dem neuen Namensraum Naturals[Int1] an. Naturals[Int1] ist dabei eine Instanz (bzw. Kopie) des Namensraumes Naturals, die durch das explizite Renaming im Importbefehl geschaffen wurde. Natürlich können auch instanziierte Namesräume bei einem weiteren Import manipuliert werden:

   import Integers[Int2]{  public : INT renamed to INTnew  }

INTnew gehört, wie auch beispielsweise der hier nicht mehr sichtbare Name 0, nun dem

Namensraum Naturals[Int1,Int2] an.

Die hierachische Struktur einer Spezifikation bedingt Abhängigkeiten zwischen Namensräumen. Im Beispiel führt die Umbenennung von INT des Namensraumes Naturals[Int1] nach INTnew auch zu einer Änderung der Konstruktordeklaration für p des Namensraumes Integers (Definitions- und Wertebereich werden geändert), der Namensraum Booleans bleibt dagegen unbeeinflußt. ASF^{+} trägt diesem Umstand Rechnung, indem der Konstruktor p und die Variablen x und y aus Integers dem neuen Namensraum Integers[Int2] zugeordnet werden. BOOL gehört nach wie vor dem Namensraum Booleans an. Allgemein hängt ein moduleigener Namensraum von allen importierten Namensräumen ab, was bei der Modifikation von Namen aus indirekt importierten Modulen zur Instanziierung mehrerer Namensräume führt.

Jede Instanzbezeichnung darf innerhalb einer Spezifikation nur ein einziges mal verwendet werden. Da zwischen Modulkürzeln und Instanzbezeichnungen keine Verwechselungsgefahr besteht, bietet es sich an, das Modulkürzel als Instanzbezeichnung wiederzuverwenden, sofern im Modul nur ein instanziierender Import vorgenommen wird.

2.5 Parameterbindungen

module OrdNatSequences
short  ONSeq
{
   import OrdSequences[ONSeq] <(ITEMpar bound to NAT,
                               ordpar  bound to greater) of OrdNaturals >
   {  public: SEQ renamed to NSEQ,
              nil renamed to Nnil,
              cons, greater,
              BOOL, true, false  }
   import OrdNaturals
   {  public: NAT, greater, 0, s  }
}

Analog zu ASF werden Parameter blockweise an ein Modul gebunden. Semantisch gesehen bedeutet die Bindung von Parametern eines Moduls M_{F ORM} (im Beispiel OrdSeqences) an Namen eines Moduls M_{ACT} (im Beispiel OrdNaturals) einerseits, daß Parameternamen aus M_{F ORM} durch Namen aus M_{ACT} ersetzt werden. Letztere können entweder exportierbare Signaturnamen oder Parameter sein. Da sie jedoch nur im Kontext des Moduls M_{ACT} eine Bedeutung besitzen, müssen andererseits beide Module miteinander kombiniert werden. Der Importblock, der der Parameterbindung folgt, bestimmt ausschließlich die Sichtbarkeit der Signaturnamen des Moduls M_{F ORM}. Explizites Renaming ist zulässig. Die Signaturnamen des Moduls M_{ACT} (auch die aktuellen Parameter selbst, sofern sie nicht wieder Parameter sind) gelten im bindenden Modul (im Beispiel OrdNatSequences) als verdeckt, es sei denn, ein weiterer (direkter) Import nimmt wie im Beispiel Einfluß auf die Sichtbarkeit einzelner Namen. Explizites Renaming ist in diesem Falle jedoch kaum sinnvoll, weil sonst die Signaturnamen des zusätzlich importierten Moduls aufgrund der unterschiedlichen Namensrauminstanzen nicht mit denen aus M_{ACT} identifiziert werden.

Genau wie das explizite Renaming führt auch das Binden von Parametern zur Instanziierung der direkt betroffenen und aller davon abhängigen Namensräume. Um auch ohne den direkten Import von M_{ACT} eine vollständige Signatur zu garantieren, sorgt die Semantik der Parameterbindung dafür, daß neben dem Modul M_{F ORM} auch automatisch M_{ACT} (verdeckt) in das bindende Modul importiert wird — ein Vorgang, der im folgenden als impliziter Import bezeichnet wird.

greater kann als Demonstrationsbeispiel für eine überladene Funktion gesehen werden. In OrdNatSequences referenziert der Name sowohl eine irreflexive Ordnung auf den natürlichen Zahlen als auch auf Sequenzen.

Da jede Parameterbindung zu einer Instanziierung des Namensraumes der zu bindenden Parameter und aller davon abhängigen Namensräume bis hin zum Modul M_{F ORM} führt (diese Namensräume fallen zusammen, falls wie in unseren Beispielen M_{F ORM} die Parameter selbst definiert, also nicht importiert), ist es auch möglich, Module “an sich selbst” zu binden, ohne daß es zu einer unerwünschten Vermischung der dort eingeführten Strukturen kommt.

SeqOfSeq spezifiziert Sequenzen von Elementen, die selbst Sequenzen sind. Um Namenskollisionen zwischen Signaturnamen der Module M_{F ORM} und M_{ACT} zu vermeiden ist eine die Umbenennung aller Sorten und Konstanten, deren Sichtbarkeit im bindenden Modul erwünscht ist (im Beispiel SEQ und nil) zwingend notwendig. Die sowohl aus M_{F ORM} als auch aus M_{ACT} importierten Konstruktoren cons unterscheiden sich in ihren Argumentsorten und dürfen daher überladen werden.

module SeqOfSeq <(ITEMpar)>
short  SOS
{
   import Sequences[SOS] <( ITEMpar bound to SEQ )
                            of Sequences <(ITEMpar)> >
   {  public: SEQ renamed to SEQ1,
              nil renamed to nil1,
              cons  }
   import Sequences <(ITEMpar)>
   {  public: SEQ, nil, cons  }
}

3 Strukturdiagramme

Die modulare Struktur von ASF^{+}-Spezifikationen kann mit Hilfe von Strukturdiagrammen veranschaulicht werden. Alle Namen innerhalb eines importfreien Moduls gehören demselben (moduleigenen) Namensraum an. Er wird durch ein Rechteck, genannt Namensraumbox dargestellt, in dem zentriert unter der Oberkante die Namensraumbezeichnung (= Modulname) steht.

                                 Booleans

Enthält das darzustellende Modul Importbefehle, so kann der Import durch ineinander verschachtelte Boxen dargestellt werden. Sie symbolisieren die hierarchische Struktur der Namensräume, die im Modul, dessen moduleigener Namensraum durch die äußerste Box gegeben ist, eine Rolle spielen. Ein Namensraum ist von allen Namensräumen abhängig, die seine Box umschließt.

                                 Booleans Naturals

Im add signatur–Konstrukt eines Moduls enthaltene Parametertupel werden oberhalb der Boxen für importierte Namensräume in Sechsecken aufgeführt.

                              ITEMpar, ordpar OrdSequences Booleans

Werden beim Import Namen eines Moduls geändert oder Parameter gebunden, führt das in ASF^{+} dazu, daß alle direkt betroffenen, sowie die davon abhängigen Namensräume mit der Instanzbezeichnung des Importbefehls instanziiert werden. Eine fehlerhafte Identifikation von Namen aus diesen manipulierten Räumen mit den “Originalen” ist dadurch ausgeschlossen.

                              Booleans Integers Naturals[Int1]

Das Binden von Parametern eines Moduls M_{F ORM} (im Bsp. OrdSequence) an Namen eines weiteren Moduls M_{ACT} (im Beispiel OrdNaturals) wird durch einen Pfeil angedeutet. Die Richtung des Pfeils verdeutlicht die Abhängigkeit zwischen den Namensräumen der Module M_{ACT} und M_{F ORM}. Der instanziierte Namensraum der zu bindenden Parameter sowie alle von ihm abhängigen Namensräume hängen von jedem in M_{ACT} enthaltenen Namensraum ab.

                            [figure: OrdNaturals; Naturals; Booleans; Booleans; Booleans; Naturals; OrdNaturals; Booleans; ITEMpar, ordpar; OrdSequences[ONSeq]; OrdNatSequences]

Da neben dem impliziten, durch die Parameterbindung verursachten Import von M_{ACT} ein zusätzlicher (direkter) Import erforderlich ist, um Signaturnamen aus M_{ACT} für das bindende Modul sichtbar zu machen führen wir eine kompaktere Darstellung ein, in der wir den impliziten und den direkten Import (falls vorhanden) zu einer Box zusammenfassen.

                            [figure: Booleans; Naturals; OrdNaturals; Booleans; ITEMpar, ordpar; OrdSequences[ONSeq]; OrdNatSequences]

Erweitert werden können die ASF^{+}-Strukturdiagramme durch Hinzunahme der Signatur. Jedes Modul zerfällt zunächst in zwei Bereiche. Links stehen die sichtbaren, rechts die verdeckten Signaturnamen. Der linke Bereich der sichtbaren Namen zerfällt seinerseits in zwei Sichtbarkeitsstufen: Neben den public–deklarierten Namen, die vom betreffenden Modul exportiert werden können (auf die also importierende Module zugreifen können), gibt es noch die private–deklarierten Namen, welche nur innerhalb des Moduls sichtbar sind und auch nur dort referenziert werden können. Insgesamt existieren also die drei Bereiche “public”, “private” und “hidden”, die durch zwei gepunktete senkrechte Trennungslinien dargestellt werden können.

           NAT 0 s eq greater geq                                and or and false BOOL trueOrdNaturals Naturals Booleans false                                       not Booleans BOOL true                                        not + or

Im Beispiel sind innerhalb von Naturals die Namen NAT, 0, s, eq, + und die importierten Namen false, BOOL, true sichtbar. Nach dem Import in OrdNaturals bleiben davon zunächst lediglich die Namen NAT, 0, s, eq und false übrig. +, BOOL, und true werden hier hingegen nicht sichtbar. Der zweite Import des Moduls Booleans sorgt dafür, daß auch für OrdNaturals BOOL und true sichtbar sind. Hauptzweck dieses Imports ist es jedoch, die Referenzierbarkeit von or für OrdNaturals zu erreichen, was beim indirekten Import über Naturals nicht möglich war. Am Beispiel wird deutlich, daß bei mehrfachem Import desselben Moduls ein Name in unterschiedlichen Sichtbarkeitsstufen auftreten kann und wird. Die Sichtbarkeit im importierenden Modul richtet sich bei ASF^{+} in diesem Fall nach der größten importierten Sichtbarkeit (Auftreten am weitesten links im Strukturdiagramm). Gleichnamige Parameter, gleichnamige Sorten sowie gleichnamige Funktionen mit gleichen Argumentsorten, die innerhalb eines Moduls sichtbar sind und unterschiedlichen Namensräumen angehören, stellen einen Namenskonflikt, also einen Spezifikationsfehler, dar.

4 Semantik hierarchischer Konzepte

Für hierarchische Konzepte algebraischer Spezifikationssprachen sind grundsätzlich zwei Semantikansätze denkbar:

• Jedes Modul erhält eine Semantik. Die Semantik einer hierarchisch modularisierten Spezifikation errechnet sich aus den einzelnen Modulsemantiken.

• Nur für elementare (flache) Spezifikationen wird eine algebraische Semantik definiert. Hierarchischen Spezifikationen wird mit Hilfe eines Normalform-Algorithmus eine elementare Spezifikation zugewiesen, deren Semantik die Semantik der hierarchischen Spezifikation definiert. Die Bedeutung der Importkonstrukte ist hier eine auf der Syntax von Spezifikationsmodulen und nicht auf deren Semantiken definierte Funktion.

Obwohl hinsichtlich der Modularisierung von Beweisen die erste Variante interessante Perspektiven bietet, fällt unsere Wahl aufgrund der hohen Komplexität und der vielen offenen Fragen in bezug auf praktische Adäquatheit einer geeigneten Modulsemantik auf die zweite. Ein Vorteil dieser auch bei ASF angewandten Vorgehensweise ist die gute Operationalisierbarkeit. Von zentraler Bedeutung ist die Normalisierungsprozedur, da mit ihr (indirekt) die Semantik der einzelnen Importbefehle festgelegt wird. Im folgenden sollen grundsätzliche Möglichkeiten beleuchtet, Schwachstellen der ASF-Semantik erläutert und Alternativen aufgezeigt werden.

4.1 Der “benutzende” Import

Lassen wir zunächst das Verdeckungskonzept außer acht und verzichten außerdem auf die Möglichkeit, Funktionen zu überladen. Dann kann man sich die Bedeutung eines renamingfreien Importbefehls ohne Parameterbindungen in erster Näherung als eine “komponentenweise” Vereinigung des importierten Moduls mit dem importierenden Modul vorstellen. Die Sortennamenmenge des resultierenden Moduls ergibt sich als Vereinigungsmenge der Sortennamen des importierten und des importierenden Moduls. Gleiches gilt für Konstruktor- und Non-Konstruktor-Funktionsdeklarationen, Parametertupel, Variablendeklarationen, Gleichungen, Beweisziele und, mit Ausnahme des gerade ausgewerteten Importbefehls (der nun gelöscht werden kann), auch für die Importbefehle. Der Modulname des resultierenden Moduls ist durch die komponentenweise Vereinigung nicht festgelegt. Die Normalform einer mit Hilfe solcher Importbefehle hierarchisch strukturierten Spezifikation berechnet sich dann als komponentenweise Vereinigung aller direkt und indirekt importierten Module mit dem Top-Modul. Die Reihenfolge, mit der die Importbefehle eleminiert werden, spielt dabei für das resultierende Normalformmodul keine Rolle. Ein Spezifikationsfehler liegt vor, wenn bei der Vereinigung ein inkorrektes Modul erzeugt wird.

Alternativ können die Importbefehle eines Moduls auch in zwei Schritten eleminiert werden: Zunächst werden die importierten Module untereinander und danach das Zwischenresultat mit dem importierenden Modul “vereinigt”. Dieses Vorgehen liefert bei der bisher betrachteten eingeschränkten Form von Importbefehlen das gleiche Resultat. Die Vereinigung der Importbefehlmenge muß in diesem Fall sinngemäß modifiziert werden: Bei der komponentenweisen Vereinigung im ersten Schritt (wir schreiben ⨆) müssen alle Importbefehle der vereinigten (importierten) Module im Zwischenresultat berücksichtigt werden, während im zweiten Schritt (hier schreiben wir ⊔) nur die Importbefehle des Zwischenresultats (und nicht die des importierenden Moduls) in das Resultat übernommen werden dürfen.

Dies erlaubt nun die folgende Operationalisierung der Vereinigungssemantik, welche den Vorteil hat, daß alle Zwischenergebnisse Normalformen sind, was bei der Behandlung von verdeckten Namen von Vorteil ist.

• Die Normalform eines importfreien Moduls ist das Modul selbst.

• Die Normalform eines Moduls M, welches M_{1}, …, M_{n} importiert ergibt sich aus der komponentenweisen Vereinigung der Normalformen von M_{1}, …, M_{n} und M.

Wir schreiben:

NF (M) := \{ \begin{matrix}M & falls M importfrei \\ M ⊔ ⨆_{i=1}^{n} NF (M_{i}) & falls M_{1}, …, M_{n} von M importiert werden. \end{matrix}

Die Vereinigungssemantik ist invariant gegenüber mehrfachem Import des gleichen Moduls, auch ist die Reihenfolge der Importe ohne Bedeutung. Entscheidend bleibt lediglich, welche Module importiert werden. Diese Eigenschaften sind typisch für eine bestimmte Art von Importen, die wir “benutzende Importe” nennen.

Die Einfachheit der Vereinigungssemantik wird jedoch mit einem schweren Defekt erkauft. Sie identifiziert Sorten und Funktionsdeklarationen aus verschiedenen Herkunftsmodulen im Falle zufälliger syntaktischer Gleichheit, auch wenn sie nichts miteinander zu tun haben. Nur wenige Konflikte zwischen Modulen, die den gleichen Namen in unterschiedlicher Bedeutung benutzen, werden erkannt.

Eine modifizierte Version der “Vereinigungssemantik” sollte also prüfen, ob es innerhalb der Spezifikation einen Namen gibt, der in zwei Modulsignaturen unterschiedlich definiert wird. In diesem Fall (wir gehen von sichtbaren, nicht überladbaren Namen aus) liegt ein Namenskonflikt vor. Diese Modifikation kann auf die rekursive Variante nicht ohne weiteres übertragen werden, da den Signaturnamen der Normalformen der zu importierenden Module nicht direkt angesehen werden kann, welchem Modul sie ihre Entstehung verdanken.

                 M1 M2 M3                           M1' M2' M3' M4' M4' A A                             A A

In obigem Beispiel tritt der Sortenname A sowohl links in den Signaturen der Normalformen von M_{2} und M_{3} (sie sind bereits in Normalform) als auch rechts in den Signaturen der Normalformen von M′_{2} und M′_{3} auf. Während dies in M_{1} zu einem Namenskonflikt führt, können in M′_{1} beide Namen identifiziert werden, da sie aus der gleichen Definition in M′_{4} hervorgegangen sind. Den Normalformmodulen ist das jedoch nicht mehr zu entnehmen. ASF löst das Problem durch Einführung einer Originfunktion. Sie weist jedem Signaturnamen einen Informationsblock (Origin) zu, der u. a. den Namen des Moduls enthält, welches für die Definition des Namens verantwortlich ist. Tritt in zwei zu importierenden Normalformmodulen der gleiche Signaturname auf, kann anhand der zugeordneten Origins entschieden werden, ob es sich um einen Namenskonflikt handelt oder nicht.

Ein modifizierter Normalformalgorithmus könnte folgendermaßen aussehen: Sei {M_{i} | 1 ≤ i ≤ n} die Menge der vom Spezifizierer erzeugten Module einer Spezifikation, modn_{i} der Name des Moduls M_{i} und {sign_{i,j} | 1 ≤ j ≤ m_{i}} die Menge aller Signaturnamen des Moduls M_{i}. Wir definieren zu jedem Modul eine Originfunktion

Ur_{i} : {sign_{i,j} | 1 ≤ j ≤ m_{i}} ⟶ {modn_{i}}
Ur_{i} := {sign_{i,j} | 1 ≤ j ≤ m_{i}} × {modn_{i}}.

Die Normalform eines Moduls M_{i} errechnet sich rekursiv wie folgt:

NF (M_{i}) := \{ \begin{matrix}(M_{i}, Ur_{i}) & falls M_{i} importfrei \\ (M_{i} ⊔ ⨆_{k=1}^{p_{i}} M′_{i′_{k}}, Ur_{i} ∪ ⋃_{k=1}^{p_{i}} o′_{i′_{k}}) & falls M_{i} die Module M_{i′_{k}} importiert und (M′_{i′_{k}}, o′_{i′_{k}}) = NF (M_{i′_{k}}) gilt (1 ≤ k ≤ p_{i}). \end{matrix}

Ein Namenskonflikt liegt genau dann vor, wenn (Ur_{i} ∪ ⋃_{k=1}^{p_{i}} o′_{i′_{k}}) keine Funktion ist.

Der angegebene Algorithmus liefert genau die Semantik renamingfreier Importe ohne Parameterbindung für sichtbare nicht überladene Namen aus ASF bzw. ASF^{+}.

4.2 Der “kopierende” Import

Besonders in großen Spezifikationen wird es häufig zu Namenskonflikten kommen, weil die Zahl der Namen mit jedem neuen Modul wächst. Würde das Auflösen solcher Konflikte das Edieren der verantwortlichen Module erzwingen, zöge das gleichzeitig Namensänderungen in allen Modulen nach sich, die auf das edierte Modul zugreifen. Der Spezifizierer hätte bei der Erstellung eines neuen Moduls darauf zu achten, daß alle neu eingeführten Namen in keinem anderen bisher vorhandenen Modul verwendet werden, was der Konzeption des modularen Spezifizierens nicht entspricht. Deshalb stellt ASF ein Renamingkonstrukt zur Verfügung, welches das Umbenennen von Namen beim Import ermöglicht. Leider führt jedoch die ASF-Bedeutung dieses Konstrukts zum Vermischen unterschiedlicher Strukturen, wie das folgende ASF-Beispiel zeigt:

module exA
begin
   exports
      begin sorts A
            functions
               mk_A : -> A
      end
end exA

   Die Anweisung “ imports exA { renamed by [mk A -> make A] } ” bedeutet in ASF

den Import eines Moduls namens exA, das sich vom Original exA dadurch unterscheidet, daß jedes Auftreten vom Signaturnamen mk A durch make A ersetzt wurde. Das erscheint sinnvoll, solange innerhalb einer Spezifikation nur mit einer Version des Moduls gearbeitet wird. Äußerst unschön erweist sich die Semantik jedoch beim Import mehrerer Varianten eines Moduls:

Module Murks
begin
   imports exA,
           exA {  renamed by [mk_A -> make_A]  }
end Murks

Die Semantik von ASF kann zwischen beiden Instanzen des importierten Moduls exA nicht unterscheiden, was dazu führt, daß Murks über zwei Konstruktoren für die Sorte A verfügt. Das namenweise Kopieren kann in größeren Spezifikationen leicht dazu führen, daß Namen, die nicht direkt am expliziten Renaming beteiligt sind, fälschlich miteinander identifiziert werden.

Zu derartig unmotivierten Namensidentifikationen kommt es in ASF auch beim Import verschiedener, durch Parameterbindungen aktualisierter Versionen des gleichen Moduls. Als Demonstrationsbeispiel untersuchen wir Sequenzen über natürlichen Zahlen und Boole’schen Werten in ASF:

module Sequences
begin
   parameters
      Items begin
               sorts ITEM
            end Items
   exports
      begin
         sorts SEQ
         functions nil :            -> SEQ
                   cons: ITEM # SEQ -> SEQ
      end
end Sequences

module Auwei
begin
    imports Sequences
              {  Items bound by [ITEM -> NAT]  to Naturals  },
            Sequences
              {  Items bound by [ITEM -> BOOL] to Booleans  }
end Auwei

Die Module Naturals und Booleans seien sinngemäß (analog zu den gleichnamigen ASF^{+}-Modulen) definiert. Das Modul Auwei importiert zwei verschiedene Arten von Sequenzen. Beide Arten tragen jedoch den gleichen Sortennamen SEQ, was eigentlich einen Namenskonflikt erwarten ließe. Statt dessen werden jedoch von ASF beide Sorten miteinander identifiziert, was dazu führt, daß cons(s(0), cons(true, nil)) als wohlsortierter Term der Sorte SEQ akzeptiert wird. Dies entspricht sicherlich nicht den Vorstellungen des Spezifizierers!

Lassen wir weiterhin verdeckte Namen und Overloading außer acht, dann kann das Renaming aus ASF als Erweiterung der modifizierten Vereinigungssemantik gesehen werden:

NF (M_{i}) := \{ \begin{matrix}(M_{i}, Ur_{i}) & falls M_{i} importfrei \\ (M_{i} ⊔ ⨆_{k=1}^{p_{i}} R_{i′_{k}}(M′_{i′_{k}}), Ur_{i} ∪ ⋃_{k=1}^{p_{i}} R_{i′_{k}}(o′_{i′_{k}})) & falls M_{i} die Module M_{i′_{k}} impor- tiert und (M′_{i′_{k}}, o′_{i′_{k}}) = NF (M_{i′_{k}}) gilt (1 ≤ k ≤ p_{i}). \end{matrix}

Hier ist R_{i′_{k}} eine Funktion, die Signaturnamen des zu importierenden Moduls nach Maßgabe des Renamingkonstrukts (falls vorhanden) durch andere ersetzt, und auf Module und Originfunktionen angewendet werden kann. Falls der Importbefehl für das Modul M_{i′_{k}} kein Renamingkonstrukt enthält, ist R_{i′_{k}} die Identität.

Unverändert bleiben in dieser Erweiterung (wie auch bei der hier nicht formalisierten Erweiterung für Parameterbindungen) die Modulnamen im Wertebereich der Originfunktionen. So ist es zwar einerseits möglich, vorhandene Module zu modifizieren, andererseits können diese verschiedenen Aktualisierungen dann nicht unterschieden werden, was bei Mehrfachimporten zu ungewünschter Vermischung der Strukturen führt.

ASF^{+} geht hier einen anderen Weg. Die Modulnamen im Wertebereich der Originfunktion werden als Namensraumbezeichnungen interpretiert. Manipulationen wie explizites Renaming oder das Binden von Parametern stellen einen schwerwiegenden Eingriff in die den beteiligten Namen zugeordneten Namensräume dar. Um sicher zu stellen, daß Namen aus den veränderten Namensräumen nicht mit Namen des ursprünglichen Namensraumes identifiziert werden, ordenet ASF^{+} den veränderten Namensräumen neue Bezeichnungen zu. Diese setzen sich aus den alten Bezeichnungen und den Instanzbezeichnungen der instanziierenden Importbefehle zusammen. Wir sagen: Die Namensräume werden instanziiert.

Ein interessanter Fall tritt ein, wenn durch Renaming oder Parameterbindung ein Modul verändert wird, das selbst weitere Module importiert, dessen (Signatur-) Namen also verschiedenen Namensräumen angehören. Ein undifferenziertes Instanziieren aller Namensräume würde zu zahlreichen überflüssigen Namenskonflikten führen. Beispielsweise beeinflußt das Binden des Parametertupels von OrdSequences (siehe Seite 7) beim Import das indirekt importierte Modul Booleans in keinster Weise, so daß der Identifikation der Sorte BOOL mit dem Orginal (welches möglicherweise mittels weiterer Befehle importiert wird) nichts entgegen steht. Andererseits können Manipulationen, die beim kopierenden Import vorgenommen werden auch indirekt importierte Teilsignaturen betreffen. In diesem Fall genügt es nicht, nur die Namensräume der direkt betroffenen Signaturnamen zu instanziieren. Vielmehr müssen ebenfalls alle Namensräume, die von den instanziierten Namensräumen abhängen bis hin zum Namensraum des direkt importierten Moduls instanziiert werden.

Allgemein führt das Manipulieren von indirekt importierten Modulsignaturen zur Instanziierung mehrerer Namensräume. Zur Illustration betrachten wir ein ASF^{+}-Beispiel:

   module exA
   {
      add signature{  public: sorts A  }
   }
   module exAB
   {
      import exA   {  public: A  }
      add signature{  public: sorts B  }
   }
   module exABC
   {
      import exAB  {  public: A, B  }
      add signature{  public: sorts C  }
   }
   module CopyDemo
   {
      import exABC[Copy]{  public: A,
                                  B renamed to Bnew,
                                  C  }
      import exABC      {  public: A  }
      import exABC      {  public: C  }
   }
                                                        exABC exAB exA C       A B exABC exAB exA A       B C exA exAB[Copy] exABC[Copy] Bnew A C  CopyDemo

Der erste Importbefehl von CopyDemo manipuliert die Signatur des (indirekt) importierten Moduls exAB. Dies führt zu einer Instanziierung des zugeordneten Namensraumes — Bnew gehört nun dem neuen Namensraum exAB[Copy] an. Auf die Signatur des ebenfalls (indirekt) importieren Moduls exA hat das keinen Einfluß, daher können die Sorten A aus den ersten beiden Importbefehlen identifiziert werden und nach wie vor dem Namensraum exA angehören. Eine Manipulation in der Signatur von exAB hat Einfluß auf die Signatur des exAB importierenden Moduls exABC, weil hier die veränderten Namen sichtbar sind und im allgemeinen auch in den Funktionsdeklarationen auftreten werden. ASF^{+} ordnet dem Sortennamen C im ersten Importbefehl den instanziierten Namensraum exABC[Copy] zu. Im dritten Importbefehl gehört C dagegen dem (nicht modifizierten) Namensraum exABC an. Die Identifikationsregel sieht darin einen Namenskonflikt und wird die vorliegende Spezifikation nicht akzeptieren. ASF hingegen würde die beiden Sorten C identifizieren, was im allgemeinen die weiter oben bereits aufgezeigten Probleme bereitet.

In ASF^{+} bleibt der Namenskonflikt auch dann bestehen, wenn der erste Importbefehl durch

   import exABC[Copy]{  public: A, B renamed to B, C  }

ersetzt wird. Der Ausdruck name_{1} renamed to name_{2} hat im Kontext eines ASF^{+}-Imports also zwei verschiedene Auswirkungen. Neben der Zugriffsänderung bewirkt er auch eine Instanziierung eines oder mehrerer Namensräume. Ist man nur an letzterer Wirkung interessiert, kann ein Ausdruck name renamed to name sinnvoll sein, was in ASF^{+} auch als copy of name geschrieben werden kann.

In beiden Fällen löst sich der Namenskonflikt auf, wenn der dritte Importbefehl in CopyDemo entfernt wird.

Die Realisation der hier vorgestellten Semantik erfordert zwei Änderungen in der modifizierten Vereinigungssemantik. Zunächst muß der Wertebereich der Originfunktionen auf Namensraumbezeichnungen ausgedehnt werden. Sie setzen sich aus Modulnamen und Instanzbezeichnungen zusammen. Die Originfunktion des normalisierten Moduls exABC kann beispielsweise folgendermaßen dargestellt werden: {(A,exA), (B,exAB), (C,exABC)}. Neben der Umbenennung von B zu Bnew verändert das explizite Renaming auch den Wertebereich der Originfunktion: {(A,exA), (Bnew,exAB[Copy]), (C,exABC[Copy])}. Um ermitteln zu können, welche Namensräume instanziiert werden müssen, wird außerdem Information über den hierarchischen Aufbau der Spezifikation benötigt. Aus diesem Grund führen wir zur Erklärung der Semantik von ASF^{+} eine zusätzliche Funktion namens Dependenzfunktion ein, die jedem innerhalb eines Moduls auftretenden Namensraum die Menge aller Namensräume zuordnet, die von ihm abhängen. Sie wird im folgenden Abschnitt 4.3 diskutiert.

Die hier vorgestellte Sorte von Importen bezeichnen wir als kopierende Importe. Ihre Verwendung ist immer dann sinnvoll, wenn “wesentliche” Eigenschaften der importierten Struktur geändert werden sollen. Was aber sind “wesentliche” Eigenschaften? Neben den bereits diskutierten Signaturmanipulationen (explizites Renaming und Parameterbindung) können im importierenden Modul auch neue Konstruktoren und Funktionen zu einer importierten Sorte bereitgestellt und im Gleichungsblock neue Beziehungen zwischen Elementen der importierten Struktur definiert werden (z. B. zwecks Erweiterung partiell definierter Funktionen). All dies hat Einfluß auf die Gültigkeit von Klauseln. Würde man in allen Fällen kopierenden Import verlangen, hätte das zur Folge, daß bewiesene Beweisziele eines Moduls beim benutzenden Import des Moduls in ein anderes ihre Gültigkeit behalten würden — ein denkbar einfacher Beweismodularisierungsansatz. Formal erscheint diese “seiteneffektfreie” Semantik des benutzenden Imports optimal. Auch kann die Einhaltung der Restriktionen vom Normalformalgorithmus syntaktisch geprüft werden. Andererseits scheinen die Bedingungen für praktischen Gebrauch zu restriktiv, weil unnötig viele Namen und Instanzbezeichnungen den Blick auf das Wesentliche versperren. ASF^{+} schreibt den kopierenden Import nur bei Manipulationen durch Renaming und Parameterbindung vor und überläßt in allen anderen Fällen dem Spezifizierer die Wahl des Importtyps.

Die folgende Spezifikation einer zyklischen Gruppe mit drei Elementen Nat3 kann als Anschauungsbeispiel dafür dienen, wie der kopierende Import auch über Renaming und Parameterbindung hinaus als Spezifikationshilfsmittel sinnvoll eingesetzt werden kann:

module Nat3
{
    import Naturals[Nat3]
    {  public: copy of NAT,
               0, s, +  }
    variables  {  x: -> NAT  }
    equations  {  [e1] s(s(s(x))) = x  }
}

Die Gleichung e1 nimmt destruktiven Einfluß auf die importierte Datenstruktur. Würden hier die Namen NAT und s mit den Originalen aus Naturals identifiziert, so stände das unverfälschte Original für die gesamte Spezifikation nicht mehr zur Verfügung. Mit Einführung zusätzlicher Restriktionen (z. B. “Verbot des Auftretens von Termen aus ausschließlich benutzend importierten Funktionssymbolen als linke Seite einer Gleichung im equations– Block.”) könnte der kopierende Import von Naturals erzwungen werden.

4.3 Abhängigkeiten zwischen Namensräumen

Die hierarchische Struktur der Spezifikation bedingt Abhängigkeiten zwischen den erzeugten Namensräumen. Die Semantik von ASF^{+} wird ihnen durch Einführung einer sogenannten Dependenzfunktion gerecht, welche der Bezeichnung jedes Namensraumes die Menge der Bezeichnungen aller von ihm abhängigen Namensräume zuweist. Diese Dependenzfunktion soll hier diskutiert werden.

Ein importfreies Modul M namens modn enthält nur einen Namensraum, nämlich den moduleigenen. Seine Bezeichnung stimmt mit dem Modulnamen überein, Abhängigkeiten zu anderen Namensräumen bestehen nicht. Die zugehörige Dependenzfunktion lautet also

depf := {(modn, ∅)}.

Der moduleigene Namensraum eines nicht importfreien Moduls M namens modn ist von allen importierten Namensräumen abhängig. Die zugehörige Dependenzfunktion depf kann aus den Dependenzfunktionen, die sich aus den einzelnen Importbefehlen ergeben, berechnet werden. Zu diesem Zweck definieren wir eine Hilfsfunktion CombineDependencies, die eine Menge von Dependenzfunktionen zu einer Funktion zusammenfaßt.

CombineDependencies({depf_{i} | i ∈ A}) := { (modinst, ⋃_{i∈B} depf_{i}(modinst)) | modinst ∈ ⋃_{i∈A} Dom(depf_{i}) ∧

B = {i ∈ A | modinst ∈ Dom(depf_{i})} }

Mit Hilfe von CombineDependencies kann nun die Dependenzfunktion depf für beliebige, nicht notwendigerweise importfreie Module definiert werden:

depf := { (modn, ∅) } ∪

{ (modinst, modinstances ∪ {modn}) | (modinst, modinstances) ∈ CombineDependencies({depf-imp-const_{i} | 1 ≤ i ≤ l}) } wobei depf-imp-const_{i} die zum i-ten Importbefehl des Moduls M zugehörige Dependenzfunktion beinhaltet (1 ≤ i ≤ l). Wie die zu einem Importbefehl zugehörige Dependenzfunktion depf-imp-const aus der Dependenzfunktion depf-imp-mod des importierten Moduls zu berechnen ist hängt vom Importtyp ab und wird im folgenden erklärt.

Handelt es sich um einen benutzenden Import des Moduls M-imp und ist depf-imp-mod die Dependenzfunktion des Moduls, so gilt depf-imp-const := depf-imp-mod.

Handelt es sich dagegen um einen kopierenden Import von M-imp, in dem explizites Renaming durchgeführt wird, dann geht depf-imp-const aus depf-imp-mod dadurch hervor, daß jedes Auftreten von Bezeichnungen der vom Renaming direkt betroffenen Namensräume sowie der von diesen bezüglich depf-imp-mod abhängigen Namensräume durch die mit der Instanzbezeichnung des Importbefehls instanziierten Namensraumbezeichnung ersetzt wird.

Werden formale Parameter an Namen aus k aktuellen Modulen M-act_{j} (1 ≤ j ≤ k) gebunden, so sind zusätzlich die Namensraumbezeichnungen aller formalen Parameter, an die aktuelle Parameter gebunden werden, sowie alle bezüglich depf-imp-mod von ihnen abhängigen Namensräume in depf-imp-mod zu instanziieren. Die resultierende Funktion nennen wir depf-imp-mod′. Des weiteren sind die Namensraumabhängigkeiten der implizit importierten aktuellen Module depf-act-mod_{j} zu berücksichtigen. Sei analog zum expliziten Import

depf-imp-const := CombineDependencies(\begin{matrix}\{depf-imp-mod′\} ∪ \\ \{depf-act-mod′_{j} | 1 ≤ j ≤ k\}). \end{matrix}

Würde man hier depf-act-mod′_{j} mit depf-act-mod_{j} gleichsetzen, so entsprächen die aus implizitem Import resultierenden Abhängigkeiten innerhalb des bindenden Moduls denen eines benutzenden Imports. Unberücksichtigt blieben dabei jedoch die Beziehungen zwischen dem Modul der formalen Parameter (hier M-imp) und den Modulen der aktuellen Parameter (hier M-act_{j}). Dies ist jedoch erforderlich: Werden beispielsweise bei einem späteren Import des bindenden Moduls (hier M) aktuelle Parameter aus der Bindung umbenannt, so hat dies auch Einfluß auf die Namen des Moduls der formalen Parameter. Allgemein definieren wir daher:

depf-act-mod′_{j} := { (modinst, modinstances ∪ {paradefmodinst_{j}} ∪

depf-imp-mod′(paradefmodinst_{j}) |

(modinst, modinstances) ∈ depf-act-mod_{j} },

wobei paradefmodinst_{j} der Namensraum der an Namen des Moduls M-act_{j} zu bindenden formalen Parameter des Moduls M-imp ist.

Im folgenden Beispiel werden die Abhängigkeiten zwischen den Namensräumen aus OrdNatSequences durch die zugehörige Dependenzfunktion dargestellt. Sie kann auch aus dem in Kapitel 3 vorgestellten Strukturdiagramm gewonnen werden.

   { (Booleans, { Naturals, OrdNaturals, OrdSequences[ONSeq], OrdNatSequences }),
    (Naturals, { OrdNaturals, OrdSequences[ONSeq], OrdNatSequences }),
    (OrdSequences[ONSeq], { OrdNatSequences }) }

4.4 Verdeckte Namen

Im Prinzip könnten alle Namenskonflikte, die beim Import von Modulen auftreten, durch “explizite” Umbenennungen (s. o.) aufgelöst werden. Allerdings erfordert dies vom Spezifizierer einen Überblick über alle eingeführten Namen, was mit zunehmender Spezifikationskomplexität immer schwieriger wird. Um den Spezifizierer vom Umbenennen “unwichtiger” Namen zu entlasten, unterscheidet ASF sichtbare und verdeckte Namen. Während die Konfliktlösung zwischen sichtbaren Namen weiterhin in der Verantwortung des Spezifizierers liegt, werden Konflikte zwischen verdeckten Namen vom Normalformalgorithmus durch automatisches Umbenennen (implizites Renaming) aufgelöst.

ASF beschränkt die Referenzierbarkeit verdeckter Namen jeweils auf das definierende Modul, was für Variablen (sie können in diesem Sinne als verdeckt betrachtet werden) ausreichend ist. Um die Zahl der Konflikte zwischen Sorten- und Funktionsnamen wirksam zu reduzieren, erscheint diese Einschränkung jedoch zu restriktiv. Wünschenswert wäre ein Mechanismus, der es erlaubt, Namen, die in der jeweiligen Spezifikationsebene nicht mehr gebraucht werden, “auszublenden”. Modulare Programmiersprachen stellen zu diesem Zweck Ex- und Importlisten zur Verfügung.

ASF^{+} übernimmt die Importlisten (alle weiterhin sichtbaren Namen müssen im Importkonstrukt aufgeführt werden). Das Exportverhalten von Namen wird dagegen direkt in der Definition bzw. beim Import durch die Schlüsselworte private und public festgelegt. Dies reduziert den Code und dient der Übersicht. Werden Namen beim Import verdeckt, so ersetzt der Normalformalgorithmus alle diese Namen durch neue, innerhalb der gesamten Spezifikation eindeutige Namen. Zu diesem Zweck wird dem alten vom Spezifizierer vereinbarten Namen der (abgekürzte) Name des entsprechenden Namensraumes gefolgt von einem Bindestrich vorangestellt. Beispielsweise werden die Namen and, or und not aus Booleans beim verdeckten Import in das Modul Naturals durch Bo-and, Bo-or und Bo-not ersetzt. Neben der Trennungsfunktion zwischen Namensraum und ursprünglichem Namen garantiert der Bindestrich die Konfliktfreiheit zwischen verdeckten und sichtbaren Namen, da er in letztgenannten nicht zugelassen ist.

Besonders nützlich erweist sich das Instrument der Namensverdeckung in Verbindung mit dem kopierenden Import, bei dem zahlreiche Namensumbenennungen erforderlich werden, da Signaturnamen aus verschiedenen Instanzen eines Moduls nicht miteinander identifiziert werden dürfen. ASF^{+} erledigt das für die verdeckten Namen automatisch, der Spezifizierer muß sich lediglich um die sichtbaren, ihn interessierenden Namen kümmern.

4.5 Overloading

Bisher gingen wir davon aus, daß jeder Signaturname genau ein Signaturobjekt (Sorte oder Funktion) spezifiziert. In der Praxis ist es jedoch sehr nützlich, wenn verschiedene Objekte mit dem gleichen Namen referenziert werden können. Beispielsweise schreibt man gewöhnlich die Summe zweier Zahlen x und y als (x + y), egal, ob es sich bei x und y um natürliche, ganze oder rationale Zahlen handelt. Die tatsächliche Bedeutung des Namens “+” ergibt sich aus dem Kontext.

Das aus ASF übernommene Overloading gestattet es, Funktionsnamen zu überladen, wenn diese sich in ihren Argumentsorten unterscheiden. Die Restriktion erlaubt es, durch Bottom-Up-Sortenprüfung jedem Funktionsnamen innerhalb eines Terms eine eindeutige Funktion zuzuordnen. Um überladene Funktionen behandeln zu können, müssen wir im Definitionsbereich der Originfunktion zu disambiguierten Namen übergehen. Dabei handelt es sich um Tupel (specname, sortvector) bestehend aus dem Signaturnamen und einem (für n-stellige Funktionen n-dimensionalen) Sortenvektor. Jede Funktionsdeklaration im add signature-Konstrukt definiert genau einen neuen disambiguierten Namen. Der Import eines Funktionsnamens zieht im allgemeinen den Import mehrerer disambiguierter Namen nach sich. Ist als Ergebnis der Normalisierung eine überladungsfreie Spezifikation gewünscht, kann dies erreicht werden, indem alle Funktionsnamen des Normalformmoduls durch eine geeignete Repräsentation ihrer disambiguierten Namen (z. B. +[NAT,NAT]) ersetzt werden.

5 Syntax

Die Syntax von ASF^{+} ist gegeben durch folgende kontextfreie Grammatik^{1}:

<specification> ::= <module>^{+}
<module> ::= “module” <module-name>[ “<” <parameter-block>^{+} “>” ]

[ “short” <short-module-name> ] “{” <import>* [ <add-signature> ] [ <variables> ] [ <equations> ] [ <goals> ] “}”

<parameter-block> ::= “(” (<sort-or-func-name> “,”)^{+} “)”
<sort-or-func-name> ::= <sort-name> | <function-name>
<import> ::= “import” <module-name>[ “[”<instance-name>“]” ]

[ “<” <ext-para-block>^{+}“>” ] [ <import-block> ]

<ext-para-block> ::= “(”(<name-with-ren> “,”)^{+} “)”
| “(”(<sort-or-func-name> “bound to” <sort-or-func-name> “,”)^{+}

“)” “of” <module-name>[ “<” <parameter-block>^{+} “>” ]

<name-with-ren> ::= <sort-or-func-name> [ “renamed to” <sort-or-func-name> ]
| “copy of” <sort-or-func-name>
<import-block> ::= “{” [ “public:” (<name-with-ren> “,”)^{+} ] [ “private:” (<name-with-ren> “,”)^{+} ] “}”
<add-signature> ::= “add signature”
“{” [ “parameters:” <para-block-sig>^{+} ] [ “public:”  <signature> ] [ “private:”  <signature> ] “}”

<para-block-sig> ::= “(” <signature> [ “conditions” <clause>^{+} ] “)”
<signature> ::= [ “sorts” (<sort-name> “,”)^{+} ]
[ “constructors”  <function-dec>^{+} ]
[ “non-constructors” <function-dec>^{+} ]
<function-dec> ::= (<ext-func-name> “,”)^{+} “:” (<sort-name> “#”)*

“->” <sort-name>

<ext-func-name> ::= <function-name> [ “ ” ]
| “ ” <function-name> “ ”
<clause> ::= “[” <label>“]” (<eq> “,”)* “-->” (<eq> “,”)*
<eq> ::= <term> [ “=” <term> ]

1Wir kennzeichenen Terminale durch Anführungszeichen und Typewriterfont und Nichtterminale durch spitze Klammern (<…>). x* bedeutet null, eine oder mehrere und x^{+} eine oder mehrere Wiederholungen von x, (x “ts”)* und (x “ts”)^{+} stehen für Wiederholungen von x, getrennt durch das Terminalsymbol ts.

Optionale Zeichenketten sind in eckige Klammern ([…]) eingefaßt.

<term> ::= [ <term> <function-name> ] <primary>
<primary> ::= <function-name>[ “(” (<term> “,”)^{+} “)” ]
| <variable-name>
| “(”<term>“)”
| <function-name> <primary>
<variables> ::= “variables”

“{” [ [ “constructors” ] <variable-dec>^{+} ] [ “non-constructors” <variable-dec>^{+} ] “}”

<variable-dec> ::= (<variable-name> “,”)^{+} “:” “->” <sort-name>
<equations> ::= “equations”

“{” <equation>^{+} “}”

<equation> ::= “[” <label>“]” <eq> [ “if” (<eq> “,”)^{+} ]
| <macro-equation>
<goals> ::= “goals”

“{” <clause>^{+} “}”

Lexikalisch gelten in der Syntax von ASF^{+} folgende Konventionen:

• Als Trennzeichen zwischen den einzelnen lexikalischen Token sind erlaubt: Leerzeichen, horizontaler Tabulator, carriage return, Zeilen- und Seitenvorschub sowie jede Kombination dieser Zeichen.

• Modulnamen, -kürzel, Instanzbezeichnungen, Marken- und Sortennamen (also <modulename>, <short-module-name>, <instance-name>, <label> und <sort-name>) bestehen aus einer beliebigen Folge von Zahlen, Buchstaben, Apostroph “'”) und Unterstrich (“ ”). Jedoch darf der Unterstrich weder am Anfang, noch am Ende eines Namens stehen.

• In Funktonsnamen (<function-name>), die hier auch die Operatoren aus ASF beinhalten, sind zusätzlich folgende ASCI-Zeichen zulässig: “!”, “$”, “%”, “&”, “+”, “∗”, “;”, “?”, “∼”, “\”, “|”, “/”, “.”.

• Die Schlüsselworte “if”, “equation”, “else”, “case”, “renamed”, “bound”, “sorts” und “constructors” stehen als Namen nicht zur Verfügung.

Man beachte, daß in benutzerdefinierten Modulen Sorten-, Funktions- und Markennamen keinen Bindestrich (“-”) enthalten dürfen. Andernfalls wären Namenskonflikte zwischen benutzerdefinierten und verdeckten, vom Normalformalgorithmus erzeugten Namen nicht auszuschließen.

6 Die Normalform-Prozedur

Im Mittelpunkt dieses Kapitels steht der Algorithmus, mit dessen Hilfe beliebige ASF^{+}- Spezifikationen, bestehend aus einem Topmodul und einer Folge von direkt, indirekt und implizit importierten Modulen, in flache, importfreie Spezifikationen umgewandelt werden können. Besonderen Wert wurde auf die möglichst konsequente Verwendung disambiguierter Namen gelegt. Die Formalisierung des ASF zugrunde liegenden Algorithmus in [Bergstra&al.89], Kapitel 1.3.2, läßt hier einige Fragen offen^{2}. Schwerwiegender ist dagegen das (nicht dokomentierte) Fehlverhalten des ASF-Normalformalgorithmus bei mehrfachem Import namensgleicher Sorten und Funktionen mit unterschiedlicher Sichtbarkeit:

module exhiddenA
begin
   sorts A
end exhiddenA
module exA
begin
   exports begin sorts A end
end exA
module Certain-Clash
begin
   imports exhiddenA, exA
end Certain-Clash

Daß die Normalisierung von ASF hier einen Namenskonflikt ausgibt, erscheint genauso unverständlich wie die Tatsache, daß er sich durch Änderung der Importreihenfolge in Certain-Clash beheben läßt. Zwar wird in der Beschreibung der Hilfsfunktion combine^{3} darauf hingewiesen, daß verdeckte Namen des ersten Arguments mit sichtbaren Namen des zweiten Arguments kollidieren können, ein Hinweis auf die kaum akzeptablen Auswirkungen auf die Kombination mehrerer zu importierender Module (im Beispiel exhiddenA und exA) fehlt jedoch völlig.

Der gleiche Fehler führt zusammen mit dem nur unpräzise formalisierten impliziten Renaming sogar dazu, daß Namenskonflikte zwischen Namen, die durch die Normalisierung überhaupt erst erzeugt wurden, nicht auszuschließen sind:

module exAhiddenA
begin
   exports begin sorts A end
   imports exhiddenA
end exAhiddenA

2Beispielsweise ist der zweite Wert eines RENAMING-Tupels (x,y) im allgemeinen kein Element aus SFV. Trotzdem wird ihm in der Beschreibung von rename visibles ein Origin zugeordnet.

3Siehe [Bergstra&al.89], Absatz 1.3.2.2.3

module exB
begin
   sorts B
end exB
module Possible-Clash
begin
   imports exAhiddenA, exB
end Possible-Clash

Im Zuge der Normalisierung wird zunächst exAhiddenA in Normalform gebracht. Die dabei notwendige Umbenennung der verdeckt importierten Sorte A erledigt die Funktion rename hiddens. Da sie keine Kenntnis über das Modul exB hat, steht einer Ersetzung des Namens A durch B aus Sicht des Algorithmus nichts im Wege. In diesem Fall aber liefert die Normalisierung von Possible-Clash wieder einen Namenskonflikt (gleiche Situation wie oben).

Grund für die Namenskonflikte beider Beispiele ist die Asymetrie der Hilfsfunktion combine, die beim kombinieren zweier Module zwecks Konfliktvermeidung nur Umbenennungen innerhalb eines Modules vornehmen darf und sowohl bei der Kombination von Importen untereinander, als auch mit dem importierenden Modul selbst Verwendung findet.^{4} Wir ersetzten combine durch zwei verschiedene Varianten: CombineImports kombiniert zwei importierte Module untereinander. Ihre Argumente (zwei Module in Normalform) werden gleich behandelt, somit ist die Reihenfolge der Importanweisungen belanglos. CombineWith-Imports entspricht in etwa combine aus ASF — sie kombiniert das importierende Modul mit der Kombination aller Importe.

6.1 Datenstrukturen

Bevor der Normalformalgorithmus vorgestellt werden kann, müssen zunächst die Daten erläutert werden, auf denen er operiert. Als Basistyp beschränken wir uns auf Zeichenketten. Sie werden in Mengen- und Strukturtypen, die wir als Tupel mit unterschiedlichen Komponententypen darstellen werden, zu komplexeren Datenstrukturen zusammen gesetzt. Funktionen werden als Mengen repräsentiert: f = {(x, y) | y = f(x)}. P(X) bezeichnet die Potenzmenge von X, also die Menge aller Teilmengen.

Ziel der Normalisierung ist die Transformation einer ASF^{+}-Spezifikation, bestehend aus einzelnen ASF^{+}-Modulen, in eine neue importfreie ASF^{+}-Spezifikation. Neben den Typen ASF-MODULE und ASF-SPEC werden für die Eingabeschnittstelle der Normalisierungsprozedur auch Informationen über bereits geführte Beweise benötigt. Sie werden im Typ PROVE-DB zusammengefaßt.

• ASF-MODULE ist die Menge aller Zeichenfolgen, die syntaktisch korrekte ASF^{+}- Module darstellen.

• ASF-SPEC := ASF-MODULE × P(ASF-MODULE)

ASF^{+}-Spezifikationen bestehen aus einem Topmodul und einer Menge von Modulen, die mindestens alle vom Topmodul direkt, indirekt und implizit importierten Module enthalten muß.

4Siehe [Bergstra&al.89], Absatz 1.3.2.3, 4. Schritt des Algorithmus

• PROVE-DB ist eine nicht näher konkretisierte Wissensbasis für gelungene Beweise. Mit ihrer Hilfe wird die Gültigkeit von semantischen Bedingungen für Parameterbindungen geprüft.

Allerdings eignet sich die Repräsentation eines ASF^{+}-Moduls als unstrukturierte Zeichenkette kaum zur adäquaten Beschreibung der für die Transformation notwendigen Operationen (z. B. Kombination mehrerer Module). Wir führen daher einen strukturierten Datentyp MODULE ein, der es ermöglicht, auf einzelne Teile eines repräsentierten Moduls (z. B. auf die Importbefehle) direkt zuzugreifen. Die kleinsten logischen Einheiten eines Moduls bestehen aus Namen, die in Abhängigkeit vom Kontext ihres Auftretens als Modulnamen oder -kürzel, als Marken, Instanzbezeichnungen, Variablen-, Sorten- oder als Funktionsnamen dienen. Unter den Sorten- und Funktionsnamen besitzen wiederum in einer Parametersignatur definierten Namen einen Sonderstatus, sie heißen Sorten- und Funktionsparameter. Einige Namen werden während der Normalisierung verändert oder zur Veränderung anderer Namen gebraucht. Um die Zahl der Namenstypen möglichst überschaubar zu halten, fassen wir Namen, auf denen die gleichen Operationen ausgeführt werden, gruppenweise zusammen:

• MODULE-NAME ist Menge aller Modulnamen.

• SHORT-MODULE-NAME ist Menge aller abgekürzten Modulnamen. Sie enthält alle Modulkürzel sowie die Namen der Module, für die kein Kürzel angegeben worden ist. Unter dem “abgekürzten Namen eines Moduls” verstehen wir das im Modul vereinbarte Kürzel, oder (falls nicht vorhanden) den Modulnamen selbst.

• INST-NAME ist Menge aller Instanzbezeichnungen.

• USER-NAME ist die Menge aller dem Spezifizierer zur Verfügung stehenden Namen für Parameter, Sorten, Funktionen, Variablen und Marken.

Neben den vom Spezifizierer erzeugten Namen generiert der Normalformalgorithmus auch selbstständig Namen, die sich (im Gegensatz zu ASF) aus den vom Spezifizierer vorgegebenen Namen und Kürzeln zusammensetzen. Das modulare Konzept aus ASF^{+} basiert wesentlich auf der Zuordnung von Namen zu Namensräumen. Eine Namensraumbezeichnung besteht aus einem Modulnamen und einer gegebenenfalls leeren Liste von Instanzbezeichnungen, welche Auskunft darüber gibt, um welche Version des Namensraumes es sich handelt. Auch Namensraumbezeichnungen können mit Hilfe der Modulkürzel abgekürzt werden.

• MODINST-NAME enthält alle Namensraumbezeichnungen. Syntaktisch kann MODINST-NAME durch eine Grammatik-Produktionsregel wie folgt beschrieben werden:

MODINST-NAME ::= MODULE-NAME
| MODULE-NAME“[”(INST-NAME “,”)^{+}“]”

• SHORT-MODINST-NAME enthält alle abgekürzten Namensraumbezeichnungen.

SHORT-MODINST-NAME ::= SHORT-MODULE-NAME
| SHORT-MODULE-NAME“[”(INST-NAME “,”)^{+}“]”

Wird beim Import ein Name verdeckt, so ersetzt der ASF^{+}-Normalforalgorithmus den Namen durch einen neuen, innerhalb der gesamten Spezifikation eindeutigen Namen, indem er dem alten Namen eine abgekürzte Namensraumbezeichnung gefolgt von einem Bindestrich voranstellt. Der so erzeugte Name ist kein USER-NAME, kann also mit keinem vom Spezifizierer eingeführten Namen in Konflikt geraten.

• SPEC-NAME umfaßt alle Parameter-, Sorten-, Funktions-, Variablen- und Markennamen, die nach der Normalisierung in der Spezifikation auftreten können. Zur Charakterisierung der Syntax geben wir wieder eine Produktionsregel an:

SPEC-NAME ::= USER-NAME | SHORT-MODINST-NAME“-”USER-NAME

ASF^{+} gestattet es, Funktionsnamen zu überladen. Um eine spezielle Funktion identifizieren zu können ist deshalb die Kenntnis der Argumentsorten erforderlich. Dies führt uns zu disambiguierten Namen:

• SORT-VECTOR ist eine Menge von Listen, deren Komponenten Sortennamen (∈ SPEC-NAME) sind.

• DISAMB-SPEC-NAME := SPEC-NAME × SORT-VECTOR umfaßt die Menge der disamiguierten Namen. Disambiguierte Namen sind Tupel (name, sortv). Falls name ein Sorten-, Marken-, Variablen oder Konstantenname ist, ist der Sortenvektor sortv leer. Handelt es sich dagegen um einen Funktionsnamen (bzw. Funktionsparameter) enthält er die Namen der Argumentsorten.

Die Datenstruktur für die Importe nimmt alle, aus den Importkonstrukten hervorgehenden Informationen auf, gruppiert sie nach den Erfordernissen der sequenziellen Auswertung jedoch neu:

• VISIBILITY-FUNC := USER-NAME ⟶ {“public”, “private”} Sie bestimmt die Sichtbarkeit von Signaturnamen beim Import eines Moduls. Da ASF^{+} verlangt, daß alle nach dem Import sichtbaren Namen im Importkonstrukt aufgeführt werden müssen, kann sie direkt aus der Importanweisung bestimmt werden. Namen aus dem importierten Modul, die keine Parameter sind und denen keine Sichtbarkeitsstufe ∈ {“public”, “private”} zugewiesen wird, werden beim Import verdeckt.

• RENAMING-FUNC := USER-NAME ⟶ SPEC-NAME Werden Sorten- und Funktionsnamen durch eine Funktion aus RENAMING-FUNC auf andere Sorten- und Funktionsnamen abgebildet, so beschreibt diese Funktion explizites Renaming. Handelt es sich dagegen im Definitionsbereich ausschließlich um Parameter, so kann mit ihrer Hilfe eine Parametertupelbindung beschrieben werden:

• BINDING-BLOCK := RENAMING-FUNC × MODULE-NAME Tupel (binding, modn) dieses Typs repräsentieren einen Block des Importbefehls, der über binding das Binden von Parametern eines Tupels an Signaturnamen eines Moduls namens modn beschreibt.

• IMPORT := MODULE-NAME × INST-NAME × VISIBILITY-FUNC × RENAMING-FUNC × P(BINDING-BLOCK) Elemente dieses Typs repräsentieren Importbefehle. Es handelt sich hier also um eine strukturierte Repräsentation einer Zeichenkette, die den Import in ASF^{+} beschreibt. Wird ein benutzender Import dargestellt, ist die zweite Komponente leer.

Zur Veranschaulichung sei als Beispiel folgender Importbefehl gegeben:

   import Sequences[NSeq] <(ITEMpar bound to NAT) of Naturals>
   {  public:  SEQ renamed to NSEQ
      private: nil renamed to nnil, cons  }

Wir erhalten folgende Tupeldarstellung:

   ( “Sequences”, “NSeq”,
     {(“SEQ”, “public”), (“nil”, “private”), (“cons”, “private”)},

{(“SEQ”, “NSEQ”), (“nil”, “nnil”)},

     {({(“ITEMpar”, “NAT”)}, “Naturals”)} )

Während Importkonstrukte nur Teil einer nicht normalisierten Spezifikation sind, ist das Auftreten von Signaturen, Variablenvereinbarungen, Klauseln und Gleichungen unabhängig vom Grad der Normalisierung:

• SIG := P(SPEC-NAME) × P(DISAMB-SPEC-NAME × SPEC-NAME)^{2} Dieser Datentyp repräsentiert eine Teilsignatur eines Moduls. Teilsignaturen bestehen aus einer Menge von Sortennamen und je einer Mengen von Deklarationen für Konstruktoren und Non-Konstruktoren. Jede Deklaration wird durch einen disambiguierten Namen (Funktionsname + Argumentsorten) und die zugehörigen Zielsorte repräsentiert.

• VAR-SORT-FUNC := SPEC-NAME ⟶ SPEC-NAME Die Variablenvereinbarung eines Moduls beschreibt eine Funktion, die jedem Variablennamen eine Sorte zuweist.

• CLAUSE
• EQUATION

Mit Hilfe der so definierten Strukturen kann nun ein ASF^{+}-Modul wie folgt als 9-Tupel repräsentiert werden:

• MODULE := MODULE-NAME × P(IMPORT) × P(SIG × P(CLAUSE)) × SIG^{2} × VAR-SORT-FUNC^{2} × P(EQUATION) × P(CLAUSE) Dem Modulnamen folgen die Importe, eine Menge von Parametersignaturen (mit Bedingungsklauseln), die exportierte (public) und die nur innerhalb des Moduls sichtbare (private) Signatur, zwei Funktionen, die den Konstruktor- bzw. Non-Konstruktor-Variablen ihre jeweilige Sorte zuweisen, eine Menge von spezifizierenden Gleichungen und schließlich eine Menge von Beweiszielen.

Wir können nun präzisieren, was wir im Folgenden unter der komponentenweisen Vereinigung einer Menge von Modulen verstehen werden:

Sei {module_{i} | i ∈ A} eine Menge von Modulen und es gelte für i ∈ A

module_{i} = ( modiname_{i}, imports_{i}, parameters_{i}, (sorts_{pub,i}, const_{pub,i}, non-const_{pub,i}), (sorts_{pri,i}, const_{pri,i}, non-const_{pri,i}), varsortfunc_{const,i}, varsortfunc_{non-const,i}, equations_{i}, goals_{i} ).

⨆_{i∈A} module_{i} :=

( ∅, ⋃_{i∈A} imports_{i}, ⋃_{i∈A} parameters_{i},

( ⋃_{i∈A} sorts_{pub,i}, ⋃_{i∈A} const_{pub,i}, ⋃_{i∈A} non-const_{pub,i}),

( ⋃_{i∈A} sorts_{pri,i}, ⋃_{i∈A} const_{pri,i}, ⋃_{i∈A} non-const_{pri,i}),

⋃_{i∈A} varsortfunc_{const,i}, ⋃_{i∈A} varsortfunc_{non-const,i},

⋃_{i∈A} equations_{i}, ⋃_{i∈A} goals_{i} )

Im Zuge der Importelemination geht Information über den hierachischen Aufbau der Spezifikation und die Herkunft der Signaturnamen verloren. Um dieses Wissen der Normalisierungsprozedur zugänglich zu machen, werden eine Origin- und eine Dependenzfunktion eingeführt:

• ORIGIN := USER-NAME × MODINST-NAME × {“label”, “variable”, “sort”, “function”}

× {“parameter”, “public”, “private”, “hidden”}

Im Prinzip würden Origins, die Auskunft über den Namensraum eines Bezeichners geben, ausreichen, um die angestrebte Semantik zu realisieren. Zur Formulierung des Normalformalgorithmus erweist es sich jedoch als zweckmäßig, weitere redundante Informationen, beispielsweise aus der Signatur aufzunehmen. In ASF^{+} werden Origins als Viertupel erklärt. Die vier Komponenten eines Origins (uname, defmodiname, symboltype, visibility) sind wie folgt definiert:

– uname enthält den Namen (∈ USER-NAME), der vom Spezifizierer für die spezifizierte Sorte, Funktion, Variable oder Marke (im folgenden als das spezifizierte Objekt bezeichnet) eingeführt wurde. Explizites Renaming verändert nicht nur den Namen selbst sondern auch den Eintrag uname des zugeordneten Origins.

– modiname gibt Auskunft über den Namensraum, dem der Name angehört.

– symboltype: Namensänderungen (sowohl implizites als auch explizites Renaming) werden im Normalformalgorithmus von ASF^{+} in zwei Stufen durchgeführt. Zunächst werden alle Sorten, Variablen und Marken umbenannt (für zugehörige Origins gilt symboltype ∈ {“label”, “variable”, “sort”}, danach folgt die Umbenennung der Funktionen. Diese Reihenfolge operationalisiert die durch Overloading bedingte rekursive Struktur der Identifikationsregel aus ASF^{+}.

– visibility: Für Sorten und Funktionen gibt es drei Sichtbarkeitsstufen:

∗ “public”: innerhalb des Moduls sichtbar und exportfähig.

∗ “private”: innerhalb des Moduls sichtbar, jedoch nicht exportfähig.

∗ “hidden”: innerhalb des Moduls verdeckt und natürlich auch nicht exportfähig.

Marken und Variablen gelten innerhalb des Moduls, in dem sie definiert werden als “private”, beim Import des Moduls werden sie verdeckt. Parameter gehören dem Sonderstatus “parameter” an und können nicht verdeckt werden.

• ORIGIN-FUNC := DISAMB-SPEC-NAME ⟶ ORIGIN Funktionen dieses Typs weisen (disambiguierten) Namen aus der Spezifikation Origins zu.

• DEPENDENCY-FUNC := MODINST-NAME ⟶ P(MODINST-NAME) Dependenzfunktionen beschreiben die Abhängigkeiten zwischen Namensräumen einer Spezifikation. Jeder Namensraumbezeichnung aus dem Definitonsbereich wird die Menge der Bezeichnungen aller abhängigen Namensräume zugewiesen.

Der rekursive Normalformalgorithmus operiert auf einer Datenstruktur, die wir “general forms” (GF) nennen:

• GF := MODULE × ORIGIN-FUNC × DEPENDENCY-FUNC General forms bestehen aus der (internen) Repräsentation eines Moduls, einer Originfunktion und einer Dependenzfunktion. Origin- und Dependenzfunktion können partiell sein in dem Sinn, daß Namen aus zu importierenden Modulen zunächst unberücksichtigt bleiben.

• NF ⊆ GF Als Normalformen bezeichenen wir alle general forms, die ein importfreies Modul, eine auf den im Modul vorkommenden Namen (∈ DISAMB-SPEC-NAME) totale Originfunktion und eine auf den Bezeichnungen aller im Modul enthaltenen Namensräume totale Dependenzfunktion beinhalten. Eine Normalform repräsentiert also nicht nur ein normalisiertes Modul sondern auch den Bauplan der Spezifikation, der das Modul seine Erzeugung verdankt.

Schließlich wird für die Normalisierungsprozedur noch eine Funktion benötigt, die Namensumbenennungen einzelner ggf. überladener Signaturnamen eindeutig beschreibt. Da sich das Exportverhalten überladener Funktionsnamen unterscheiden kann, ist eine Differenzierung nach den Argumentsorten erforderlich:

• DISAMB-RENAMING-FUNC := DISAMB-SPEC-NAME ⟶ SPEC-NAME Dieser Datentyp beschreibt Umbenennungen, die aufgrund von Änderungen der Sichtbarkeit einzelner Namen beim Import erforderlich werden. Es ist zu beachten, daß die Durchführung von Funktionsumbenennungen dieser Art in Gleichungen das Disambiguieren der Funktionssymbole jedes einzelnen Terms erfordert. Hierzu wird die Signatur des Moduls gebraucht.

6.2 Der Algorithmus

6.2.1 Globale Hilfsfunktionen für Sichtbarkeitsänderungen

Das dynamische Verdeckungsprinzip von ASF^{+} erfordert bei der Kombination verschiedener Module zahlreiche Signaturnamensumbenennungen. Jede Namensänderung zieht im allgemeinen Veränderungen in fast allen Teilen des Moduls und der Originfunktion nach sich. Der Normalformalgorithmus erledigt dies in zwei Schritten. Zuerst wird die 4. Komponente visibility der Origins aller Namen auf die Sichtbarkeitsstufe gesetzt, die die jeweiligen Namen zukünftig haben sollen. Die sich daraus ergebenden Umbenennungen im Modul und dem Definitionsbereich der Originfunktion, sowie die Neuordnung der Signatur werden dann von der Funktion MakeConsistent erledigt.

Die Vorgehensweise des hier vorgestellten Algorithmus nutzt die in der Originfunktion enthaltene Redundanz aus: Jedes Origin origin beschreibt den ihm zugeordneten (nicht disambiguierten) Namen GetSpecName(origin) eindeutig.

GetSpecName: ORIGIN ⟶ SPEC-NAME

GetSpecName((uname, modiname, *, visibility)) berechnet aus der ersten, zweiten und vierten Komponente eines Origins den zugeordneten (nicht disambiguierten) Namen (∈ SPEC-NAME).

falls visibility ∈ {“parameter”, “public”, “private”}

Setze specname := uname.

falls visibility = “hidden”

Setze shortmodiname gleich dem abgekürzten Modulinstanznamen von modiname.

specname := shortmodiname“-”uname

Rückgabewert: specname

Manipulationen der Sichtbarkeitskomponente im Wertebereich einer Originfunktion führen im allgemeinen dazu, daß die Namen des Definitionsbereichs nicht mehr zu den zugeordneten Origins passen. Sei ((name_{i}, sortv_{i}), origin_{i}) Element einer Originfunktion, dann entspricht GetSpecName(origin_{i}) dem “Sollwert” von name_{i}. Zur Namensaktualisierung im Modul und im Definitionsbereich der Originfunktion dient eine “Istwert-Sollwert”-Liste, die von GetRenaming erzeugt wird:

GetRenaming: ORIGIN-FUNC × P({“label”, “variable”, “sort”, “function”}) ⟶ DISAMB-RENAMING-FUNC

GetRenaming(originf , symboltypes) errechnet ein renaming für disambiguierte Namen. Mit dessen Hilfe kann innerhalb der Originfunktion sowie eines Normalformmoduls ein konsistenter Zustand hergestellt werden. symboltypes bestimmt, welche Namenstypen in das renaming aufgenommen werden sollen.

renaming := \{ \begin{matrix}((name, sortv), name′) | \\ (u, n, symboltype, v) = originf ((name, sortv)) ∧ \\ symboltype ∈ symboltypes ∧ \\ name′ = GetSpecName((u, n, symboltype, v)) ∧ \\ name′ ≠ name \} \end{matrix}

Rückgabewert: renaming

Bei der Beschreibung von Umbenennungen überladbarer Signaturnamen ist zu berücksichtigen, daß Sortenumbenennungen auch die Sortenvektoren der umzubenennenden (disambiguierten) Funktionsnamen beeinflussen. Aus Gründen der Übersichtlichkeit verzichten wir auf “simultanes” Umbenennen von Sorten und Funktionen, MakeConsistent behandelt Sorten und Funktionen nacheinander:

MakeConsistent: MODULE × ORIGIN-FUNC ⟶ MODULE × ORIGIN-FUNC

MakeConsistent(module, originfunc) erhält ein (normalisiertes) Modul module und eine Originfunktion originfunc deren Wertebereich zwecks Durchführung von Verdeckung oder Änderung des Exportverhaltens von Namen manipuliert wurde. Unter Zuhilfename der Funktionen GetSpecName und GetRenaming berechnet sie ein konsistentes Tupel (module′′′, originfunc′′). Durchgeführt werden Umbenennung von Namen (∈ SPEC-NAME) in module und im Definitionsbereich von originfunc, sowie der Austausch von Sortennamen und Funktionsdeklarationen zwischen der public- und private-Signatur. renaming := GetRenaming(originfunc, {“label”, “variable”, “sort”})

Berechne module′ durch Ersetzen der Sorten-, Variablen- und Markennamen in module nach Maßgabe von renaming.

Berechne originfunc′ durch Ersetzen der Sorten-, Variablen- und Markennamen im Definitionsbereich von originfunc nach Maßgabe von renaming. Betroffen sind insbesondere auch die Sortenvektoren der disambiguierten Funktionsnamen.

renaming′ := GetRenaming(originfunc′, {“function”})

Berechne module′′ und originfunc′′ durch Ersetzen der Funktionsnamen in module′ und im Definitionsbereich der Originfunktion originfunc′ nach Maßgabe von renaming′.

module′′′ entsteht aus module′′ durch Aktualisierung der public- und private-Signatur. Namen mit Sichtbarkeitsstufe “private” oder “hidden” sind nicht exportfähig und gehören in die private-Signatur. Solche mit Sichtbarkeitsstufe “public” hingegen gehören in die public-Signatur. Parameter bleiben wo sie sind, nämlich in der parameter-Signatur.

Rückgabewert: (module′′′, originfunc′′)

6.2.2 Kombination von Modulen

Der Import sowie das Binden von Parametern an Signaturnamen eines Moduls führt bei der Normalisierung dazu, daß mehrere general forms zu einer neuen general form zusammengefaßt werden müssen. Diese Aufgabe erledigen die drei Funktionen CombineImports, CombineWithImports und CombineWithActModule. Als Hilfsfunktion greifen sie auf CombineDependencies und AdaptVisibility, welche die notwendigen Sichtbarkeitsanpassungen vornimmt, zu.

AdaptVisibility kann als Identifikationsregel gelesen werden, die beim Kombinieren mehrerer Module festlegt, wann (disambiguierte) Namen miteinander identifiziert werden dürfen und unter welchen Umständen es zu Namenskonflikten kommt. Wegen der Overloading-Fähigkeit ist hierbei von Wichtigkeit, daß zuerst alle Sortenidentifikationen vorgenommen werden (Aufruf von AdaptVisibility mit symboltypes := {“sort”}). Erst danach können die Funktionsidentifikationen korrekt durchgeführt werden (symboltypes := {“function”}). Die Zweistufigkeit reduziert den Sortenvektortest auf syntaktische Gleichheit. Andernfalls müßten beim Test auf Identifizierbareit die Argumentsorten der (disambiguierten) Funktionsnamen komponentenweise (rekursiv) auf Identifizierbarkeit geprüft werden. Analog zur sogenannten “Originrule” aus ASF können wir die ASF^{+} zugrundeliegende Identifikationsregel folgendermaßen beschreiben:

Identifikationsregel: Die (disambiguierten) Namen (name_{1}, sortv_{1}) und (name_{2}, sortv_{2}) aus zwei zu kombinierenden Modulen sind genau dann zu identifizieren, wenn

• die ihnen zugeordneten Origins in den ersten drei Komponenten übereinstimmen,

• die 4. Komponenten der Origins übereinstimmen oder eine 4. Komponente den Wert “hidden”, die andere 4. Komponente dagegen “private” oder “public” enthält und

• die in sortv_{1} und sortv_{2} enthaltenen Argumentsorten (nur bei Funktionsnamen relevant) miteinander identifiziert werden können.

Man beachte, daß diese Definition nicht eigentlich rekursiv ist, da der Rückbezug nicht wiederum selbst rückbezüglich ist.

Zwischen den disambiguierten Namen (name_{1}, sortv_{1}) und (name_{2}, sortv_{2}) aus zwei zu kombinierenden Modulen kommt es genau dann zum Konflikt, wenn

• sie nicht miteinander identifiziert werden können, obwohl

• name_{1} mit name_{2} übereinstimmt und

• die in sortv_{1} und sortv_{2} enthaltenen Argumentsorten miteinander identifiziert werden können.

Wir führen noch eine Sprechweise ein, die sich bei der Behandlung von Parameterbindungen als nützlich erweisen wird.

Seien originf_{1} und originf_{2} zwei Originfunktionen. Sei (name_{1}, sortv_{1}) aus dem Definitionsbereich von originf_{1} und (name_{2}, sortv_{2}) aus dem Definitionsbereich von originf_{2}. Wir definieren: (name_{1}, sortv_{1}) referenziert bezüglich originf_{1} dasselbe Objekt wie (name_{2}, sortv_{2}) bezüglich originf_{2} (im Zeichen: (name_{1}, sortv_{1})/originf_{1} ≈ (name_{2}, sortv_{2})/originf_{2}) genau dann, wenn

• die ihnen zugeordneten Origins in den ersten drei Komponenten übereinstimmen und

• die in den Komponenten von sortv_{1} und sortv_{2} enthaltenen Argumentsorten (nur bei Funktionsnamen relevant) jeweils dasselbe Signaturobjekt referenzieren.

Die Identifikationsregel aus ASF^{+} identifiziert also Namen, die das gleiche Signaturobjekt referenzieren und deren Exportverhalten in den Importbefehlen nicht widersprüchlich festgelegt wird.

AdaptVisibility: P(NF) × P({“label”, “variable”, “sort”, “function”}) ⟶ P(NF)

AdaptVisibility(normalforms, symboltypes) sorgt für die Angleichung der Sichtbarkeit von Sorten- (“sort” ∈ symboltypes) und Funktionsnamen (“function” ∈ symboltypes) aus verschiedenen (NF-) Modulen. Gleichzeitige public- und private-Importe eines Namens weisen auf einen Spezifikationsfehler hin, weil ein Name entweder exportierbar oder nicht-exportierbar sein kann aber nicht beides gleichzeitig.

Sei {(mod_{i}, originf_{i}, depf_{i}) | 1 ≤ i ≤ p} = normalforms

Für i := 1 bis p wiederhole

Für j := i + 1 bis p wiederhole Für alle ((name_{i}, sortv_{i}), (uname_{i}, modiname_{i}, symboltype_{i}, visibility_{i})) ∈ originf_{i} wiederhole

Für alle ((name_{j}, sortv_{j}), (uname_{j}, modiname_{j}, symboltype_{j}, visibility_{j})) ∈ originf_{j} wiederhole

/* Alle Origins aller übergebenen Originfunktionen originf_{i} werden mit allen Origins aller anderen übergebenen Originfunktionen originf_{j} verglichen. */ Falls (symboltype_{i} ∈ symboltypes) und sortv_{i} = sortv_{j} und uname_{i} = uname_{j}

Falls modiname_{i} = modiname_{j}

Falls symboltype_{i} ≠ symboltype_{j}

SPEZIFIKATIONSFEHLER /* Beide disambiguierten Namen verdanken ihre Existenz derselben Definition */ Falls (visibility_{i} = “hidden” und visibility_{j} ∈ {“public”, “private”})

Setze (mit Änderung von originf_{i}) visibility_{i} := visibility_{j}

Sonst falls (visibility_{j} = “hidden” und visibility_{i} ∈ {“public”,

“private”})

Setze (mit Änderung von originf_{j}) visibility_{j} := visibility_{i}

Sonst falls visibility_{i} ≠ visibility_{j}

EXPORTIERBARKEITS-KONFLIKT Sonst falls name_{i} = name_{j}

/* Der disambiguierte Name (name_{i}, sortv_{i}) tritt in beiden Normalformen mit unterschiedlicher Bedeutung auf. */ NAMENSKONFLIKT

Für alle i ∈ {1, …, p} (mod′_{i}, originf ′_{i}, depf ′_{i}) := MakeConsistent(mod_{i}, originf_{i}, depf_{i})

Rückgabewert: {(mod′_{i}, originf ′_{i}, depf ′_{i}) | 1 ≤ i ≤ p} CombineDependencies: P(DEPENDENCY-FUNC) ⟶ DEPENDENCY-FUNC

CombineDependencies({depf_{j} | j ∈ A}) erzeugt aus den Dependenzfunktionen mehrerer zu kombinierender general forms eine neue Dependenzfunktion depf′.

/* Siehe Seite 24. */

Rückgabewert: depf′

Importe werden in ASF^{+} eleminiert, indem zunächst die Normalformen der importierten Module berechnet werden. Diese werden nach Maßgabe der Importbefehle modifiziert und instanziiert (siehe dazu den folgenden Abschnitt 6.2.3) und anschließend untereinander kombiniert. Dafür zuständig ist die Funktion CombineImports:

CombineImports: \begin{matrix}P(NF) \\ ⟶ NF \end{matrix}

CombineImports(normalforms) kombiniert mehrere Normalformen.

normalforms′ := AdaptVisibility(normalforms, {“label”, “variable”, “sort”})

normalforms′′ := AdaptVisibility(normalforms′, {“function”})

Sei {(mod_{i}, originf_{i}, depf_{i}) | i ∈ A} = normalforms′′.

mod′ := ⨆_{i∈A} mod_{i}

originf′ := ⋃_{i∈A} originf_{i}

Falls originf′ keine Funktion

NAMECLASH

depf′ := CombineDependencies({depf_{i} | i ∈ A})

Rückgabewert: (mod′, originf′, depf′)

Mit Hilfe von CombineImports wird eine Normalform erzeugt, die alle importierten Module in sich vereint. Sie wird anschließend durch Anwendung der Funktion Combine-WithImports mit der general form des importierenden Moduls kombiniert. Hier sind keine Sichtbarkeitsanpassungen mehr notwendig:

CombineWithImports: \begin{matrix}GF × NF \\ ⟶ NF \end{matrix} CombineWithImports((mod, originf , depf ), (mod_{imp}, originf_{imp}, depf_{imp})) kombiniert die general form einer Modulinstanz mit einer Normalform, die aus allen von ihr importierten Modulen errechnet worden ist.

mod′ geht aus mod durch Löschen aller Importkonstrukte hervor.

mod′′ := mod′ ⊔ mod_{imp} Der Modulname von mod′′ (erste Komponente) wird auf den für die Normalform von mod vorgesehenen Namen gesetzt. Dieser kann beispielsweise aus dem Modulnamen von mod durch Anhängen der Extension “.nf” gewonnen werden.

originf′ := originf ∪ originf_{imp} Falls originf′ keine Funktion: NAMECLASH

Sei nun modname der Modulname (1. Komponente) von mod.

depf′ := { (modname, ∅) } ∪

{ (modiname, modinames ∪ {modname}) | (modiname, modinames) ∈ depf_{imp} }

Rückgabewert: (mod′′, originf′, depf′)

Wird in einem Importbefehl die Bindung eines Parametertupels aus dem importierten Modul mod_{F ORM} an Namen eines Moduls mod_{ACT} vorgenommen, so erfordert die Auswertung das Kombinieren der zugehörigen Normalformen. Dieser implizite Import des Moduls mod_{ACT} in das Modul mod_{F ORM} unterscheidet sich von gewöhnlichen Importen, weil hierdurch ein Modul “nachträglich” in eine bereits bestehende Modulhierarchie eingepflanzt wird.

CombineWithActModule: NF × MODINST-NAME × NF ⟶ NF

CombineWithActModule((mod_{FORM}, originf_{FORM}, depf_{FORM}), paradefmod, (mod_{ACT}, originf_{ACT}, depf_{ACT})) “implantiert” die Normalform (mod_{ACT}, originf_{ACT}, depf_{ACT}) in die Normalform (mod_{FORM}, originf_{FORM}, depf_{FORM}). Dabei wird eine Abhängigkeit zwischen den Namensräumen des Moduls mod_{ACT} und dem Namensraum der formalen Parameter paradefmod aus mod_{FORM} hergestellt. Es wird davon ausgegangen, daß bereits alle Renamings in der Normalform des formalen Moduls und die Sichtbarkeitsanpassungen zwischen den Namen beider Normalformen durchgeführt worden sind.

mod := mod_{ACT} ⊔ mod_{FORM} Der Modulname modname von mod_{FORM} (erste Komponente) wird in mod übernommen.

originf := originf_{FORM} ∪ originf_{ACT} Falls originf keine Funktion: NAMECLASH depf ′_{ACT} := \{ \begin{matrix}(modiname, modinames ∪ \{paradefmod\} ∪ depf_{FORM}(paradefmod) | \\ (modiname, modinames) ∈ depf_{ACT} \} \end{matrix}

depf := CombineDependencies(depf_{FORM}, depf ′_{ACT})

Rückgabewert: (mod, originf , depf )

6.2.3 Modulmodifikationen in Importbefehlen

Werden in einem Importbefehl Namen umbenannt, Parameter gebunden oder die Sichtbarkeit von Signaturnamen verändert, so führt das semantisch dazu, daß die Normalformen der zu importierenden Module modifiziert werden müssen, bevor sie zu einer einzigen Normalform zusammengefaßt werden können. Diese Aufgabe übernehmen die Funktionen Hide, Rename und Bind mit den Hilfsfunktionen InstanciateModInstName, Instanciate, SeparateParaBlock, GetParameterRenamings und CheckSemanticConditions.

Ein wesentlicher Teil eines jeden Importbefehls sind die den Schlüsselworten “private:” und “public:” folgenden Listen von Signaturnamen. Sie geben Auskunft über die Sichtbarkeit der vom importierten Modul exportierten Signaturnamen. Mit Hilfe der Funktion Hide werden alle nicht exportierten Namen verdeckt und die Sichtbarkeit der exportierten Signaturnamen den Vorgaben des Importbefehls angepaßt.

Hide: NF × VISIBILITY-FUNC ⟶ NF

Hide((mod, originf , depf ), visibilityf ) verdeckt alle Namen mit Sichtbarkeitsstufe “private”. Namen mit Sichtbarkeitsstufe “public” werden auf die in visibilityf angegebene Sichtbarkeitsstufe gesetzt; ist keine Angabe vorhanden, erhalten sie die Sichtbarkeitsstufe “hidden”.

originf′ := { (dis-name, (uname, modinst, symboltype, visibility′)) | (dis-name, (uname, modinst, symboltype, visibility)) ∈ originf ∧ ((visibility ∈ {“hidden”, “parameter”} ∧ visibility = visibility′) ∨

(visibility = “private” ∧ visibility′ = “hidden”) ∨ (visibility = “public” ∧ ((uname ∉ Dom(visibilityf ) ∧ visibility′ = “hidden”) ∨

(uname ∈ Dom(visibilityf ) ∧ visibility′ = visibilityf (uname))))) }

(mod′, originf′′) := MakeConsistent(mod, originf′)

Rückgabewert: (mod′, originf′′, depf )

Der kopierende Import aus ASF^{+} basiert auf der Zuordnung der zu kopierenden (Signatur-) Namen zu neuen Namensräumen. Zu diesem Zweck werden neue Namensraumbezeichnungen generiert, die sich aus den alten Bezeichnungen und der Instanzbezeichnung des Importbefehls zusammensetzen.

InstanciateModInstName: MODINST-NAME × INST-NAME ⟶ MODINST-NAME

InstanciateModInstName(modiname, iname) instanziiert die Namensraumbezeichnung modiname mit der Instanzbezeichnung iname. Wurde modiname bereits mit iname instanziiert, so liegt ein Spezifikationsfehler vor.

Falls modiname ∈ MODULE-NAME /* erste Instanziierung */

imodiname := modiname“[”iname“]”

Sonst

Sei modname“[”oldinames“]” = modiname Falls iname in oldinames enthalten ist

SPEZIFIKATIONSFEHLER!

imodiname := modname“[”oldinames“,”iname“]”

Rückgabewert: imodiname

Eine Normalform repräsentiert nicht nur ein normalisiertes Modul; Origin- und Dependenzfunktion erlauben die Rekonstruktion der gesamten zugrundeliegenden Modulhierarchie. Werden Teile einer Normalform durch explizites Renaming oder Parameterbindung modifiziert, können die erforderlichen Instanziierungen auf die direkt betroffenen und die davon abhängigen Namensräume begrenzt werden.

Instanciate: NF × RENAMING-FUNC × P(BINDING-BLOCK) × INST-NAME ⟶ NF

Instanciate((mod, originf , depf ), renaming, bindingblocks, iname) instanziiert Namensraumbezeichnungen in der Normalform (mod, originf , depf ) mit der Instanzbezeichnung iname. Instanziiert werden die Bezeichnungen aller vom expliziten Renaming renaming und von den Parameterbindungen bindingblocks direkt betroffenen Namensräume, sowie alle bezüglich depf von ihnen abhängigen Namensräume.

Sei {(binding_{i}, modname_{i}) | i ∈ A} = bindingblocks

toinst := \{ modiname | \begin{matrix}((name, ∗), (∗, modiname, ∗, ∗)) ∈ originf ∧ \\ name ∈ Dom(renaming) ∪ ⋃_{i∈A} Dom(binding_{i}) \} \end{matrix}

toinst′ := toinst ∪ {depf (modinst) | modinst ∈ toinst} Berechne (mod′, originf′, depf′) durch Ersetzen jedes Auftretens einer Namensraumbezeichnung modiname ∈ toinst′

• in mod (überall dort, wo sie Teil eines verdeckten Namens ist),

• in originf (Im Definitionsbereich überall dort, wo sie Teil eines verdeckten Namens ist und in der 2. Komponente der Origins des Wertebereichs) und

• in depf (wo immer sie auftritt)

durch InstanciateModInstName(modiname, iname).

Rückgabewert: (mod′, originf′, depf′)

Rename: NF × RENAMING-FUNC ⟶ NF

Rename((mod, originf , depf ), renaming) führt explizites Renaming durch. renaming enthält die Umbenennungen aller Renaminganweisungen des Importbefehls.

Sei ren(x) := \{ \begin{matrix}y & falls (x, y) ∈ renaming \\ x & sonst \end{matrix}

und ren′ die Erweiterung von ren auf Sortenvektoren:

ren′((sortn_{1}, …, sortn_{n})) := (ren(sortn_{1}), …, ren(sortn_{n}))

mod′ wird aus mod durch syntaktisches Ersetzen aller Signaturnamen name durch ren(name) erzeugt. Man beachte daß ren nur Einfluß auf sichtbare Namen (∈ USER-NAME) hat.

SPEZIFIKATIONSFEHLER falls mod′ keine korrekte Signatur enthält. /* Ursache kann hier ein fehlerhafter Renamingbefehl sein, der dazu führt, daß ursprünglich verschiedene Namen des gleichen Namensraumes nach Durchführung des Renamings zusammenfallen. Renamings dieser Art können Funktionen mit gleichen disambiguierten Namen aber unterschiedlichen Zielsorten erzeugen. */

originf′ := \{ \begin{matrix}(ren(name), ren′(sortv)), (uname′, modiname, symboltype, visibility)) | \\ ((name, sortv), (uname, modiname, symboltype, visibility)) ∈ originf ∧ \\ (\begin{matrix}(visibility = “hidden” ∧ uname′ = uname) ∨ \\ (visibility ≠ “hidden” ∧ uname′ = ren(uname))) \} \end{matrix} \end{matrix}

Rückgabewert: (mod′, originf′, depf )

Alle folgenden Funktionen dieses Abschnitts behandeln die Auswertung einer Parametertupelbindung. Der Trivialfunktion SeparateParaBlock und der (etwas technischen) Hilfsfunktion GetParameterRenamings folgen die Hauptfunktionen CheckSemanticConditions und Bind. Die Komplexität der Funktionen folgt aus der Tatsache, daß es sich bei jeder Parametertupelbindung um einen impliziten Import (also einen Import im Import) handelt und neben den schon betrachteten Operationen (z. B. Verdecken von Namen, Instanziieren von Namensräumen) im Zuge des Testens semantischer Bedingungen und des Implantierens eines aktuellen Moduls in die bereits bestehende Modulhierarchie eines formalen Moduls eine Vielzahl neuer Rechenschritte erforderlich sind.

SeparateParaBlock: NF × P(SPEC-NAME) ⟶ NF × (SIG × P(CLAUSE)) × MODINST-NAME)

SeparateParaBlock((mod, originf , depf ), parameters) extrahiert aus der internen Moduldarstellung mod die Parametersignatur und -bedingungen der in parameters enthaltenen Parameter eines Tupels. Die Parameter werden aus dem Definitionsbereich der Originfunktion entfernt und paradefmod der Namensraum zugewiesen, dem die Parameter angehören. SeparateParaBlock ist Hilfsfunktion von Bind.

Seien

sig_{p}  = die zu extrahierende Parametersignatur,
conditions = die zu sig_{p} gehörenden Bedingungsklauseln und
mod′  = das Modul, das nach Entfernen von (sig_{p}, conditions) aus mod entsteht.

SPEZIFIKATIONSFEHLER, wenn keine Parametersignatur in mod enthalten ist, die genau alle Namen aus parameters enthält. Sei parameter ∈ parameters (*, paradefmod, *, *) := originf (parameter) /* Welcher Parameter genommen wird, hat keinen Einfluß auf paradefmod */ originf′ := \{ \begin{matrix}((name, sortv), origin) | \\ ((name, sortv), origin) ∈ originf ∧ name ∉ parameters \} \end{matrix}

Rückgabewert: ((mod′, originf′, depf ), (sig_{p}, conditions), paradefmod)

GetParameterRenamings: SIG × RENAMING-FUNC × ORIGIN-FUNC^{2} ⟶ RENAMING-FUNC

GetParameterRenamings((sorts_{p}, cons-decs_{p}, ncons-decs_{p}), binding, originf_{ACT}, originf_{ACT-AV}) berechnet die jenigen Namen, durch welche die nach der Vorschrift binding an Namen eines aktuellen Moduls zu bindenden formalen Parameter syntaktisch ersetzt werden müssen. Die errechneten Namen sind im allgemeinen nicht mit denen aus Ran(binding) identisch, weil alle Namen aus dem aktuellen Modul beim impliziten Import verdeckt werden, sofern sie nicht bereits im formalen Modul sichtbar sind. Als Argumente werden die Signatur des zu bindenden Parametertupels (sorts_{p}, cons-decs_{p}, ncons-decs_{p}), die Bindungsvorschrift binding, die Originfunktion des aktuellen Moduls originf_{ACT} und eine weitere Originfunktion originf_{ACT-AV}, die aus originf_{ACT} durch Setzen aller Namen auf die beim impliziten Import angestrebte Sichtbarkeitsstufe hervorgeht, übergeben.

Falls {(binding(sortpar), ∅) | sortpar ∈ sorts_{p}} ⊈ Dom(originf_{ACT})

SPEZIFIKATIONSFEHLER /* Sortenparameterbindung fehlerhaft. Aktuelle Sor- ten existieren nicht im aktuellen Modul */

sortpar-renaming := \{ (sortpar, name) | \begin{matrix}sortpar ∈ sorts_{p} ∧ \\ (name, ∅) ∈ Dom(originf_{ACT-AV}) ∧ \\ (name, ∅)/originf_{ACT-AV} ≈ (binding(sortpar), ∅)/originf_{ACT}^{5} \} \end{matrix}

Sei sorts_{np} die Menge aller Sortennamen, die in den Deklarationen cons-decs_{p} ∪ ncons-decs_{p} auftreten, aber nicht in sorts_{p} enthalten sind.

Falls {(sort, ∅) | sort ∈ sorts_{np}} ⊈ Dom(originf_{ACT-AV})

SPEZIFIKATIONSFEHLER /* Da Funktionsparameter nur an aktuelle Funk- tionsnamen gleicher Deklaration gebunden wer- den können, müssen die Nicht-Parameter-Sorten- namen der Funktionsparameterdeklarationen nicht nur im formalen, sondern auch im aktuellen Modul auftreten. */

renaming := { (sortpar, binding(sortpar)) | sortpar ∈ sorts_{p} } ∪ \{ (sort, name) | \begin{matrix}sort ∈ sorts_{np} ∧ \\ (name, ∅) ∈ Dom(originf_{ACT}) ∧ \\ (name, ∅)/originf_{ACT} ≈ (sort, ∅)/originf_{ACT-AV} \} \end{matrix}

Berechne cons-decs′_{p} und ncons-decs′_{p} aus cons-decs_{p} und ncons-decs_{p} durch Umbenennen aller Sorten nach Maßgabe von renaming.

Sei disfuncs_{p} = { (funcpar, sortv) | ((funcpar, sortv), sort) ∈ cons-decs′_{p} ∪ncons-decs′_{p} }

Falls {(binding(funcpar), sortv) | (funcpar, sortv) ∈ disfuncs_{p}} ⊈ Dom(originf_{ACT})

SPEZIFIKATIONSFEHLER /* Bindung der Funktionsparameter fehlerhaft, kein “wohlsortierter” aktueller Parameter vorhanden */

funcpar-renaming := \{ \begin{matrix}(funcpar, name) | \\ (funcpar, sortv) ∈ disfuncs_{p} ∧ \\ (name, sortv′) ∈ Dom(originf_{ACT-AV}) ∧ \\ (name, sortv′)/originf_{ACT-AV} ≈ (binding(funcpar), sortv)/originf_{ACT} \} \end{matrix}

par-renaming := sortpar-renaming ∪ funcpar-renaming

Rückgabewert: par-renaming CheckSemanticConditions: \underrightarrow{P(CLAUSE) × MODULE^{2} × NF × PROVE-DB}

CheckSemanticConditions(conditions, mod_{FORM}, mod_{ACT-AV}, nform_{ACT}, prove-db) prüft, ob die semantischen Bedingungen, die an die Bindung von Parametern aus mod_{FORM} an Namen des in nform_{ACT} enthaltenen aktuellen Moduls geknüpft wurden, erfüllt sind. conditions ist eine Menge von Gentzen-Klauseln, die aus den semantischen Bedingungen nach Ersetzen der formalen durch die aktuellen Parameter hervorgegangen ist. mod_{ACT-AV} ist eine Variante des aktuellen Moduls, in der die Sichtbarkeit der Signaturnamen an die Sichtbarkeit innerhalb des formalen Moduls angepaßt worden ist.

Setze (∗, ∗, ∗, ∗, ∗, varsortfunc_{const,FORM}, varsortfunc_{non-const,FORM}, ∗, ∗) := mod_{FORM}

Setze (∗, ∗, ∗, ∗, ∗, varsortfunc_{const,ACT-AV}, varsortfunc_{non-const,ACT-AV}, ∗, goals_{ACT-AV}) := mod_{ACT-AV}

varsortfunc′_{FORM} := varsortfunc_{const,FORM} ∪ varsortfunc_{non-const,FORM}
varsortfunc′_{ACT-AV}:= varsortfunc_{const,ACT-AV}∪ varsortfunc_{non-const,ACT-AV}

Für alle Gentzenklauseln condition ∈ conditions

Falls es nicht eine Gentzenklausel goal ∈ goals_{ACT-AV} und eine Variablensubstitution sub gibt mit:

• sub(goal) = condition (Die Marken werden hier nicht berücksichtigt),

• sub ist “sortenrein”, d h. für alle (x, y) ∈ sub gilt varsortfunc′_{ACT-AV}(x) = varsortfunc′_{FORM}(y),

• sub substituiert Konstruktor-Variablen mit Konstruktor-Variablen und Non-Konstruktor-Variablen mit Non-Konstruktor-Variablen, d. h. für alle (x, y) ∈ sub gilt x ∈ Dom(varsortfunc_{const,ACT-AV}) ⟺ y ∈ Dom(varsortfunc_{const,FORM}) und

• das zu goal korrespondierende Beweisziel in nform_{ACT} (kann mit Hilfe des Markennamens bestimmt werden) gilt dort als bewiesen, dh. es gibt einen entsprechenden Beweis in prove-db.

SEMANTIC ERROR: Bevor die Spezifikation akzeptiert werden kann muß (falls noch nicht vorhanden) ein entsprechendes Beweisziel in das aktuelle Modul eingefügt und dessen Gültigkeit bewiesen werden.

Rückgabewert: - Bind: NF × RENAMING-FUNC × NF × PROVE-DB ⟶ NF

Bind((nform_{FORM}, binding, (mod_{ACT}, originf_{ACT}, depf_{ACT}), prove-db) führt die Bindung eines Parametertupels durch. nform_{FORM} enthält das normalisierte, parametrisierte Modul, dessen Parameter nach der Vorschrift binding an Namen des normalisierten Moduls mod_{ACT} gebunden werden sollen.

/* Zunächst werden alle Namen aus (mod_{ACT}, originf_{ACT}, depf_{ACT}) verdeckt. */

nform′_{ACT} := Hide((mod_{ACT}, originf_{ACT}, depf_{ACT}), ∅)

/* AdaptVisibility ändert die Sichtbarkeit von Namen aus verschiedenen Modulen nach dem Prinzip der “maximalen” Sichtbarkeit. Angewandt auf nform_{FORM} und nform′_{ACT} bleibt nform_{FORM} unverändert, weil es dort keinen Signaturnamen gibt, der in nform′_{ACT} sichtbar ist. */

nforms := AdaptVisibility({nform′_{ACT}, nform_{FORM}}, {“label”, “variable”, “sort”})

{(mod_{ACT-AV}, originf_{ACT-AV}, depf_{ACT-AV})} := AdaptVisibility(nforms, {“function”}) \ {nform_{FORM}}

((mod_{FORM}, originf_{FORM}, depf_{FORM}), (sig_{p}, conditions), paradefmod) := SeparateParaBlock(nform_{FORM}, Dom(binding))

par-renaming := GetParameterRenamings(sig_{p}, binding, originf_{ACT}, originf_{ACT-AV})

Berechne mod ′_{FORM}, originf ′_{FORM} und conditions′ aus mod_{FORM}, originf_{FORM} und conditions durch Ersetzen der formalen Parameter nach Maßgabe von par-renaming.

SPEZIFIKATIONSFEHLER falls mod ′_{FORM} keine korrekte Signatur enthält.

/* Eine fehlerhafter Parameterbindung kann dazu führen, daß Funktionen mit gleichen disambiguierten Namen und unterschiedlichen Zielsorten erzeugt werden. */

CheckSemanticConditions(\begin{matrix}conditions′, mod′_{FORM}, mod_{ACT-AV}, \\ (mod_{ACT}, originf_{ACT}, depf_{ACT}), prove-db) \end{matrix}

nform_{result} := CombineWithActModule(\begin{matrix}(mod ′_{FORM}, originf ′_{FORM}, depf_{FORM}), paradefmod, \\ (mod_{ACT-AV}, originf_{ACT-AV}, depf_{ACT-AV})) \end{matrix}

Rückgabewert: nform_{result} 6.2.4 Die Normalisierungsfunktionen NF und NormalForm

Ziel dieses Abschnitts ist die Vorstellung einer Funktion NormalForm, die eine gegebene hierarchische ASF^{+}-Spezifikation in eine flache, nur aus einem Topmodul bestehende ASF^{+}-Spezifikation transformiert. NormalForm besteht im wesentlichen aus einem Aufruf der rekursiven Funktion NF . NF ist die für das Verständnis des Algorithmus grundlegende Funktion. Weiterhin werden die Trivialfunktionen ModuleText, MakeGF und Extern-ModRep benötigt.

ModuleText: MODULE-NAME × ASF-SPEC ⟶ ASF-MODULE

ModuleText(modname, spec) sucht ein Modul asf-module namens modname in spec. Falls kein solches Modul existiert: SPEZIFIKATIONSFEHLER! ModuleText ist Hilfsfunktion von NF .

Weitere Formalisierung entfällt.

Rückgabewert: asf-module

MakeGF : ASF-MODULE ⟶ GF

MakeGF (asf-module) berechnet aus einem isolierten nicht notwendig importfreien ASF^{+}-Modul einer Spezifikation eine general form (mod, originf , depf ). MakeGF ist Hilfsfunktion von NF .

Der Wert von mod wird direkt aus dem ASF-Modul ermittelt, es handelt sich hier lediglich um eine andere Repräsentationsform.

Jedem (disambiguierten) Sorten- und Funktionsnamen aus der Signatur, jedem (disambiguierten) Parameternamen aus einer der Parametersignaturen und jedem, innerhalb des Moduls auftretenen (disambiguierten) Variablen- und Markennamen wird vermittels originf ein Origin zugeordnet.^{6} originf ist zunächst partiell in dem Sinn, daß importierte (Teil-) Signaturen noch nicht in Dom(originf ) enthalten sind.

depf := ∅

Rückgabewert: (mod, originf , depf )

6Siehe dazu Seite 35

NF : MODULE-NAME × ASF-SPEC × PROVE-DB ⟶ NF

NF (modname, spec, prove-db) berechnet rekursiv die Normalform nform_{result} der zum Modul namens modname zugehörigen general form.

importing-gf := MakeGF (ModuleText(modname, spec))

Setze ((∗, imports, …), ∗, ∗) := importing-gf

Sei {(modname_{i}, iname_{i}, visibilityf_{i}, renaming_{i}, bindingblocks_{i}) | i ∈ A} = imports

Für alle i ∈ A

nform_{i} := NF (modname_{i}, spec, prove-db)
nform′_{i} := Hide(nform_{i}, visibilityf_{i})

Falls iname_{i} = ∅ ∧ (renaming_{i} ≠ ∅ ∨ bindingblocks_{i} ≠ ∅)

SPEZIFIKATIONSFEHLER

Falls iname_{i} ≠ ∅

nform′′_{i} := Instanciate(nform′_{i}, renaming_{i}, bindingblocks_{i}, iname_{i})
nform′′′_{i} := Rename(nform′′_{i}, renaming_{i})

Für alle (binding, modname_{ACT}) ∈ bindingblocks_{i} wiederhole

nform_{ACT} := NF (modname_{ACT}, spec, prove-db)
nform′′′_{i} := Bind(nform′′′_{i}, binding, nform_{ACT}, prove-db)

nform_{result} := CombineWithImports(importing-gf, CombineImports({nform′′′_{i} | i ∈ A}))

Rückgabewert: nform_{result}

ExternModRep: MODULE ⟶ ASF-MODULE

ExternModRep(module) berechnet die ASF^{+}-Darstellung asf-module des Moduls module. Diese Funktion kann mit einer Option ausgestattet werden, die es erlaubt überladene Funktionsnamen durch eindeutige Repräsentationen ihrer disambiguierten Namen zu ersetzen. ExternModRep ist Hilfsfunktion von NormalForm.

Weitere Formalisierung entfällt!

Rückgabewert: asf-module NormalForm: ASF-SPEC × PROVE-DB ⟶ ASF-SPEC

NormalForm(spec, prove-db) berechnet aus einer modularen ASF^{+}-Spezifikation eine Spezifikation, bestehend aus einem einzigen (importfreien) Modul asf-module. Die Wissensbasis prove-db beinhaltet Informationen über gelungene Beweise und wird für die Überprüfung von semantischen Bedingungen gebraucht.

Sei modname der Name des Topmoduls aus spec. (mod, originf , depf ) := NF (modname, spec, prove-db)

asf-module := ExternModRep(mod)

Rückgabewert: (asf-module, ∅)

6.3 Ein Beispiel für ein normalisiertes Modul

Um die Arbeitsweise des Normalformalgorithmus zu veranschaulichen geben wir schließlich noch das importfreie, durch Normalisierung erzeugte Modul OrdNatSequences.nf an.

module OrdNatSequences.nf
{
add signature
{
public:
sorts
BOOL, NAT, NSEQ
constructors
true, false   :             -> BOOL
0             :             -> NAT
s             : NAT         -> NAT
Nnil          :             -> NSEQ
cons          : NAT # NSEQ  -> NSEQ
non-constructors
greater       : NAT  # NAT  -> BOOL
greater       : NSEQ # NSEQ -> BOOL
private:
non-constructors
Bo-and, Bo-or : BOOL # BOOL -> BOOL
Bo-not        : BOOL        -> BOOL
_Nat-+ _: NAT # NAT   -> NAT
Nat-eq        : NAT # NAT   -> BOOL
ONat-geq      : NAT # NAT   -> BOOL
}
variables
{  constructors
Nat-x, Nat-y, Nat-u,
ONat-x, ONat-y, ONat-u, ONat-v,
OSeq-i1, OSeq-i2, OSeq-i3              : -> NAT
OSeq-seq1, OSeq-seq2, OSeq-s1, OSeq-s2 : -> NSEQ
non-constructors
Bo-x, Bo-y                             : -> BOOL  }
equations
{
macro-equation Bo-and(Bo-x,Bo-y)
{
case
{  ( Bo-x @ true ) : Bo-y
( Bo-x @ false ): false  }

}
macro-equation Bo-not(Bo-x)
{
   case
   {  ( Bo-x @ true ) : false
      ( Bo-x @ false ): true  }
}
[Bo-e1] Bo-or(Bo-x, Bo-y) =
        Bo-not(Bo-and(Bo-not(Bo-x), Bo-not(Bo-y)))
macro-equation (Nat-x Nat-+ Nat-y)
{
   case
   {  ( Nat-y @ 0 )        : Nat-x
      ( Nat-y @ s(Nat-u) ) : s(Nat-x Nat-+ Nat-u)  }
macro-equation Nat-eq(Nat-x, Nat-y)
{  if ( Nat-x = Nat-y ) true
   else                 false  }
}
macro-equation greater(ONat-x, ONat-y)
{
   case
   {  ( ONat-x @ 0 )                     : false
      ( ONat-x @ s(ONat-u), ONat-y @ 0 ) : true
      ( ONat-x @ s(ONat-u), ONat-y @ s(ONat-v) ):
        greater(ONat-u,ONat-v)  }
}
[ONat-e1] ONat-geq(ONat-x,ONat-y) =
          Bo-or(greater(ONat-x,ONat-y), eq(ONat-x,ONat-y))

macro-equation greater(OSeq-seq1, OSeq-seq2)
{              /* lex-order of sequences */
   case
   {
      ( OSeq-seq1 @ Nnil )                  : false
      ( OSeq-seq1 @ cons(OSeq-i1, OSeq-s1),
        OSeq-seq2 @ Nnil                   ): true
      ( OSeq-seq1 @ cons(OSeq-i1, OSeq-s1),
        OSeq-seq2 @ cons(OSeq-i2, OSeq-s2) ):
        if ( greater(OSeq-i1, OSeq-i2) )
             true

else if ( OSeq-i1 = OSeq-i2 )
     greater(OSeq-s1, OSeq-s2)
else false
}
}
}
goals
{
[ONat-irref] greater(ONat-x, ONat-x)
-->
[ONat-trans] greater(ONat-x, ONat-u), greater(ONat-u, ONat-y)
--> greater(ONat-x, ONat-y)
[ONat-total]
--> greater(ONat-x, ONat-y), greater(ONat-y, ONat-x),
    ONat-x = ONat-y
}
} /* OrdNatSequences.nf */

7 Abschließende Zusammenfassung

Mit ASF^{+} ist es gelungen, eine algebraische Spezifikationssprache zu entwickeln, die neue Konzepte wie beispielsweise das differenzierte Verdecken von Signaturnamen, semantische Bedingungen an Parameter und die Angabe von Beweiszielen in sich vereint, ohne dabei auf wesentliche Elemente der bereits existierenden Sprache ASF verzichten zu müssen. Hierbei konnte die Syntax von ASF sogar noch vereinfacht werden.

ASF^{+} ist jedoch mehr als eine nur um zusätzliche Konstruke erweiterte Version von ASF. Grundsätzliche Untersuchungen (wie in Kapitel 4 dargestellt) deckten Fehler in der Semantik von ASF auf und führten zu den Begriffsbildungen “benutzender” und “kopierender Import”. Während der benutzende Import aus ASF übernommen wurde, verhindern in ASF^{+} von den kopierenden Importbefehlen zur Verfügung gestellte Instanzbezeichnungen Namensverwechselungen zwischen dem manipulierten Modul und seinem Original.

Wesentlicher Bestandteil von ASF^{+} ist das Namensraumkonzept, welches jedem Signaturnamen bei seiner Definition den Modulnamen zuordnet. Während beim benutzenden Import der Namensraum unverändert bleibt, führt der kopierende Import eines Namens zur Instanziierung des zugeordneten Namensraumes. Bei der Kombination mehrerer Module zu einem Normalformmodul werden nur solche Namen identifiziert, die dem gleichen Namensraum angehören.

Das Namensraumkonzept spielt auch in der Semantik verdeckter Namen eine wichtige Rolle. Jedem zu verdeckenden Namen wird im Zuge der Normalisierung die (abgekürzte) Namensraumbezeichnung vorangestellt. Dies erhöht die Verständlichkeit des erzeugten Normalformmoduls und macht den modularen Aufbau der Spezifikation sichtbar.

Der Preis für die Verbesserungen ist jedoch eine gewisse Verkomplizierung der Normalisierungsprozedur, was beim Vergleich des im Kapitel 6 vorgestellten Algorithmus mit dem aus [Bergstra&al.89] (Seite 23-28) deutlich wird.

Schließlich erlauben die von uns entwickelten Strukturdiagramme eine ebenso informative wie leicht verständliche Darstellung von ASF^{+}-Spezifikationen. Diese Strukturdiagramme eignen sich darüberhinaus auch dazu, ein korrektes intuitives Verständnis für die wesentlichen Konzepte der Normalisierungsprozedur — wie Originfunktion, Dependenzfunktion, Sichtbarkeitsanpassung, Renaming, Parameterbindung, Namensrauminstanziierung, etc. — zu vermitteln.

Literatur

[Bergstra&al.89]  J. A. Bergstra, J. Heering, P. Klint (1989).

Algebraic Specification. ACM Press.

[Eschbach94]  Robert Eschbach (1994).

ART — Modularisierung von Induktionsbeweisen über Gleichungsspezifikationen. SEKI-WORKING-PAPER SWP–94–03 (SFB), Fachbereich Informatik, Universität Kaiserslautern, D–67663 Kaiserslautern.

[Hendriks91]  P. R. H. Hendriks (1991).

Implementation of Modular Algebraic Specifications. PhD. Thesis, CWI (Centrum voor Wiskunde en Informatica), Amsterdam.

[Wirth&Gramlich93] Claus-Peter Wirth, Bernhard Gramlich (1993).

A Constructor-Based Approach for Positive/Negative-Conditional Equational Specifications. 3^{rd} CTRS 1992, LNCS 656, Seiten 198-212, Springer-Verlag. Überarbeitete und erweiterte Version in: J. Symbolic Computation (1994) 17, Seiten 51-90, Academic Press.

[Wirth&Gramlich94] Claus-Peter Wirth, Bernhard Gramlich (1994).

On Notions of Inductive Validity for First-Order Equational Clauses. 12^{th} CADE 1994, LNAI 814, Seiten 162-176, Springer-Verlag.

[Wirth&Lunde94]  Claus-Peter Wirth, Rüdiger Lunde (1994).

Writing Positive/Negative-Conditional Equations Conveniently. SEKI-WORKING-PAPER SWP–94–04 (SFB), Fachbereich Informatik, Universität Kaiserslautern, D–67663 Kaiserslautern.