Kapitel 1
Einführung
1.1Konstitutive Prinzipien in A++
A++ steht für Abstraktion plus Referenz plus Synthese. Diese drei Begriffe entsprechen den sprachlichen Strukturelementen und den Grundoperationen in A++ und werden deshalb im nächsten Kapitel im Zusammenhang mit der Syntax der Sprache noch ausführlich behandelt.
Zu den konstitutiven Prinzipien, d.h. den Prinzipien, die A++ wesentlich zu dem machen, was es ist, gehören außerdem noch die Begriffe ‘Closure’ und ‘Lexical Scope’. Wir werden sie der Reihe nach definieren und beschreiben.
Abstraktion
FUNDAMENTALBEGRIFF 1 (ABSTRAKTION)
Abstrahieren bedeutet: Etwas einen Namen geben. Es besteht darin, etwas Komplexes zu behandeln, als wäre es etwas Einfacheres, indem Details ignoriert werden.
Eine solche Abstraktion wird auch als Lambda-Abstraktion bezeichnet, wenn mit ihr die Definition einer Funktion verbunden ist, die zwangsläufig zur Erzeugung einer ‘Closure’ mündet. Bezüglich des letzteren Punktes siehe weiter unten die Definition des vierten konstitutiven Prinzips.
Referenz
FUNDAMENTALBEGRIFF 2 (REFERENZ)
Auf etwas, das einen Namen erhalten hat, kann jederzeit mit diesem Namen Bezug genommen werden. Diese Bezugnahme nennen wir Referenz.
Im Zusammenhang mit der Referenz ist von großer Bedeutung auf welche Namen Bezug genommen werden kann. Dies führt zum letzten konstitutiven Prinzip von A++, dem Begriff des ‘Lexical Scope’.
Synthese
FUNDAMENTALBEGRIFF 3 (SYNTHESE)
Eine Synthese zu bilden bedeutet: Zwei oder mehrere Dinge (die selbst Ergebnis einer Abstraktion sind!) miteinander zu verknüpfen um etwas Neues (Komplexes) zu schaffen.
Der Begriff der Synthese entspricht weitgehend dem des Aufrufs einer Funktion oder der Abbildung, bzw. der Applikation.
Closure
Die Bildung einer Abstraktion ist in A++ nicht ein absolutes, von Allem losgelöstes Ereignis. Eine Abstraktion erfolgt immer in einem bestimmten Kontext, der somit wesentlich zu der gebildeten Abstraktion gehört. Die Lambda-Abstraktion wird zum Zeitpunkt ihrer Erzeugung mit ihrem Kontext oder ihrer Umgebung verbunden. Das Resultat dieser Verkapselung wird ‘Closure’ genannt. Eine Closure ist eine Art der Verkapselung wie wir sie in der Objekt-Orientierung finden. In der Objekt-Orientierung sind in einem Objekt Daten und Funktionen verkapselt, die Attribute des Objektes mit dessen Methoden gemäß ausdrücklicher Festlegung in der Klassendefinition oder im Aufbau des Konstruktors.
Bei einer Closure dagegen erfolgt diese Verkapselung nicht durch willkürliche, ausdrückliche Definitionen, sondern alles, was zum textlichen Umfeld einer Funktion gehört wird automatisch in diese Verkapselung einbezogen. Wir haben deshalb in ‘Programmierung pur’ das Bild einer Muschel1. als Symbol für eine Closure gewählt.
Somit können wir eine Closure wie folgt definieren:
FUNDAMENTALBEGRIFF 4 (CLOSURE)
Eine Funktion wird eine “Closure” genannt, wenn sie mit der sie umgebenden Menge der Daten und Funktionen fest verkoppelt ist. Die Variablen der geerbten Umgebung werden “freie Variable” und die Argumente der Funktion werden als “gebundene Variable” bezeichnet. Die in der Funktion definierten Variablen heissen “lokale Variable”. Alle Variable einer echten Closure haben unbegrenzte Lebensdauer.
Eine solche Funktion kann nur in der ihr eigenen Umgebung ausgeführt werden. Wir haben hier ein Beispiel der Verkapselung von ausführbarem Code mit den dazugehörigen Daten. So etwas Ähnliches finden wir wieder in der weiter unten beschriebenen Objekt-Orientierung.
Eine “closure” kann man sich nach ihrer Definition folgendermaßen vorstellen: Siehe hierzu Abbildung 1.1 auf der nächsten Seite. Die zwei Kreise nebeneinander sind das Symbol für eine “closure”. Diese Diagrammtechnik wurde eingeführt von Harold Abelson und Gerald Jay Sussman in ihrem legendären SICP-Buch, d.h. dem offiziellen Lehrbuch der Informatik am Massachusetts Institute of Technology mit dem Titel: Structure and Interpretation of Computer Programs. Siehe hierzu im Literaturverzeichnis [AwJS96].
Abbildung 1.1: Definition einer “closure”
Abbildung 1.2: Aufruf einer “closure”
An den Programmtext fest gekoppelt ist die Umgebung des Programms, in dem die “closure” definiert wurde ( “lexical scope”). Diese Heimatumgebung der “closure” hat in allen folgenden Diagrammen das Kennzeichen ’<umgebung 1>’.
Zum Zeitpunkt der Ausführung der Funktion sieht die Sache etwas anders aus. Der Funktion wird ein neuer Umgebungsbereich zugewiesen (“environment frame”), der allerdings wiederum mit der ursprünglichen Umgebung der “closure” verknüpft ist. Dieser neue Umgebungsbereich enthält die Argumente der Funktion und die in ihr definierten lokalen Variablen. In den folgenden Diagrammen trägt sie das Kennzeichen ‘<umgebung 2>’. Siehe hierzu Abbildung 1.2!
Lexical Scope
Es kann auf bereits definierte Abstraktionen über Namen Bezug genommen werden. Auf welche Namen an welcher Stelle im Programm Bezug genommen werden kann, definiert der sogeannte Scope in einer konkreten Programmiersprache. Es gibt drei Muster nach denen die Gültigkeit von Namen in Programmteilen geregelt ist:
Lexical Scope oder Static Scope: In diesem Schema gelten die Namen nur in dem Bereich, in dem sie definiert sind. Der Gültigkeitsbereich ist direkt aus dem Programmtext ersichtlich, woher der Name “lexical scope” rührt.
Wir definieren deshalb:
FUNDAMENTALBEGRIFF 5 (LEXICAL SCOPE)
Namen gelten nur in den Funktionen, die die Namensdefinition enthalten, bzw. in solchen, die innerhalb dieser Funktion als verschachtelte Funktionen definiert wurden.
Mit dem ‘lexical scope’ kann wie in der Programmiersprache Algol ein ‘dynamic extent’ verbunden sein, oder wie in Common-Lisp und in Scheme der ‘indefinite extent’.
Letzerer bedeutet, dass alle Variablen unbegrenzte Lebensdauer haben. Die unbegrenzte Lebensdauer wird allerdings dadurch eingeschränkt, dass Variable, die von nirgendwoher mehr im Programm erreicht werden können, als Müll vom Garbage-Collector beseitigt werden.
Closures können auch gesehen werden als Funktionen mit ‘lexical scope’ und ‘indefinite extent’.
Dynamic Scope: Eine Variable kann von überallaus im Programm direkt mit ihrem Namen angesprochen werden. Dynamic Scope ist von McCarthy ursprünglich in Lisp eingeführt worden, wird aber inzwischen wegen gewaltiger Nachteile in modernen Lisp-Dialekten nicht mehr oder nur bedingt verwendet. McCarthy selbst hat eingesehen, dass es ein Design-Fehler war, dynamic scope in Lisp zu verwenden.2
Global und Local Scope: In diesem Schema hat eine Variable entweder ‘global’ oder ‘local scope’. Im ersten Falle bedeutet das, dass eine Variable überall Gültigkeit besitzt. Im zweiten Fall gilt eine Variable nur in der Funktion, in der sie definiert wurde, ohne dass sich diese Gültigkeit auf tiefere Ebenen fortpflanzen würde. Dieses System wird momentan noch in der Programmiersprache Python verwendet.
Im nächsten Kapitel wird das sprachliche Gewand eingeführt, in das wir diese drei Operationen kleiden werden, um mit ihnen programmieren zu können.
1Das englische Wort für Muschel ist bekanntlich ‘clam’. Bei uns hat ‘clam’ noch sinnvoller Weise eine andere Bedeutung, nämlich: ‘c-lambda-abstraction’. So bezeichnen wir in ARSAPI eine Lambda-Abstraktion in C. Siehe hierzu Abschnitt 8.2 auf Seite 74
2siehe Seite 180: R.Wexelblat(ed.), History of Programming Languages, Academic Press, New York, 1981.
Kapitel 2
Sprachdefinition
2.1Syntax und Semantik von A++
Um die Syntax von A++ zu definieren, werden wir die EBNF-Notation verwenden.
Zuerst folgt die Erklärung der EBNF-Notation selbst:
EBNF-Notation:
• | bedeutet „oder“
• [ . . . ] eckige Klammern bedeuten optional.
• { ... } bedeutet „0 oder n-mal“ .
• ’ ... ’ kennzeichnet Text, der wörtlich zu nehmen ist.
• ( . . . ) runde Klammern können zur Gruppierung benutzt werden.
• < . . . > Abkapslung eines Begriffs.
• empty bedeutet die leere Menge.
Syntax von A++ in EBNF-Notation: 1
Anmerkungen zur Syntax:
Zur Definition der Abstraktion in (2)
• Die erste Alternative in der Definition bezieht sich auf die allgemeine Form der Namensvergabe.
• Die zweite Alternative bezieht sich auf das, was der Namensvergabe normalerweise vorausgeht, die eigentliche Abstraktion. Die Namensvergabe ist nur der letzte Schritt.
– Es gibt auch anonyme Lambda-Abstraktionen, bei denen die Namensvergabe als überflüssig weggelassen wird.
– Der Definition einer anonymen Lambda-Abstraktion kann auch als Definition einer Funktion angesehen werden.
Zur Definition der Synthese in (4)
Eine Synthese gemäß obiger Definition wird auch oft als Funktionsaufruf oder als Abbildung (Applikation) bezeichnet.
2.2Beispiele zur Syntax von A++
Zur Veranschaulichung der Syntax-Definition in der EBNF-Notation folgen einige Beispiele:
Beispiele zur Abstraktion 1. Alternative in 2.2
Beispiele zur Abstraktion 2. Alternative in 2.2
Beispiele zur Referenz 2.3
Beispiele zur Synthese 2.4
Abbildung 2.1: A++
2.3 A++ Erweiterung
Wir erlauben uns, A++ um wenige vorgegeben Primitivabstraktionen zu erweitern. Das Ziel dieser Erweiterung ist im Wesentlichen eine Möglichkeit zu schaffen, Resultate von Programmen auf dem Bildschirm anzuzeigen, A++ - Code von einer Datei zu laden, und beliebige Abstraktionen miteinander zu vergleichen.2
Es handelt sich um die folgenden Primitiv-Abstraktionen:
• vmzero Eine Referenz auf die Zahl 0 des Computers. Sie stellt eine Brücke zwischen den A++ - Zahlen (Church-Numerals) und den Zahlen im Computer dar.
• vmtrue Eine Referenz auf den boole’schen Wert ’wahr’ im Basissystem.
• vmfalse Eine Referenz auf den boole’schen Wert ’falsch’ im Basissytem.
• double-quoted-string Mit dieser Abstraktion werden Zeichenketten in A++ eingeführt. Sie werden zwingend im Zusammenhang mit der Primitiv-Operation ‘load’ gebraucht. Diese benötigt als Argument einen Dateinamen in der Form einer Zeichenkette.
• single-quoted-string Mit dieser Abstraktion werden symbolische Konstanten in A++ eingeführt. Sie sind nicht zwingend erforderlich, aber sie erleichtern das Programmieren gewaltig, besonders in der objekt-orientierten Anwendung.
Dort wird mit Objekten über Botschaften kommuniziert. Ohne symbolische Konstanten müßte man alle Botschaften als Zahlen (Church Numerals) codieren.
• incr Eine Funktion zum Erhöhen einer Computerzahl um 1.
• print Eine Funktion zum Anzeigen einer Computerzahl oder eines boole’schen Wertes auf dem Bildschirm.
• load Funktion zum Laden einer Code-Datei. Dies ist eine nützliche Funktion beim Austesten größerer A++ - Programme. Das zu testende Programm braucht nicht interaktiv eingegeben zu werden, sondern kann mit dem Aufruf von ’load’ geladen werden.
• equalx Um außer den Church Numerals noch andere Daten wie Closures, Symbole und Zeichenketten miteinander vergleichen zu können braucht man in A++ diese zusätzliche Primitivoperation. Mit der direkt aus ARS abgeleiteten Abstraktion ‘equalp’ können nur Zahlen miteinander verglichen werden.
• quit Diese Funktion wird benutzt, um den ARS-Interpreter zu beenden.
Diese vorgegebenen Primitivabstraktionen werden in folgenden A++ - Lambda-Abstraktionen verwendet:
• ndisp! für die Ausgabe einer numerischen Lambda-Abstraktion.
• bdisp! für die Ausgabe einer boole’schen Lambda-Abstraktion.
• ldisp! für die Ausgabe einer Liste.
Das Ausrufezeichen am Ende der drei Funktionsnamen weist daraufhin, dass es Funktionen mit Nebenwirkungen sind, d.h. Funktionen, die nicht nur einen Wert zurückliefern sondern im Hintergrund noch mehr bewirken. Dieser Umstand stempelt sie zu gefährlichen Funktionen, weshalb ihre Namen mit einem Ausrufezeichen versehen sind.
Syntax von A++ mit vorgegebenen Primitiv-Abstraktionen
Der folgende EBNF-Code stellt die Definition von A++ mit Erweiterungen für die Bildschirmanzeige dar.
Syntax von A++ mit vorgegebenen Primitiv-Abstraktionen 3
Beispiele zu den Erweiterungen in A++
Abbildung 2.2: Vorgegebene Primitiv-Abstraktionen für A++
Abbildung 2.3: A++ mit vorgegebenen Primitiv-Abstraktionen
Abbildung 2.4: A++ mit primitiven Werten und Operationen sowie typischen Abstraktionen
1Siehe auch die Abbildung 2.1 auf Seite 7
2Mit der Einführung einer einzigen weiteren, leicht zu implementierenden Primitivoperation (define-macro) , könnten wir in A++ eine mächtige Makro-Technik integrieren. Letzteres würde aber die Struktur von A++ leicht verändern, worauf wir aus Gründen der in A++ angestrebten Einfachheit verzichten. Wir verschieben diesen Punkt auf ARS++, der Erweiterung von A++, die eine Sprache schafft, die Scheme-Funktionalität besitzt und noch mehr.
ARS++ wird in den Büchern ‘Programmierung pur – Programmieren fundamental und ohne Grenzen’ und ARS++ – A++ in großem Stil’ aus dreierlei Sicht ausführlich behandelt: als Sprache, Anwendungen der Sprache und Implementierung der Sprache.
3Siehe auch die Abbildung 2.3 auf Seite 11