Self (Programmiersprache)
# Self (Programmiersprache)

Self [sɛlf] ist eine Programmiersprache, die vor allem in Hinblick auf Ausdrucksfähigkeit und Formbarkeit hin von Randall B. Smith und David Ungar entworfen wurde. Um diese Ziele zu erreichen, wurde ein rein prototypenbasiertes Objektmodell mit einheitlichem Zugriff auf Zustand und Verhalten der Objekte, also auf ihre Attribute und Methoden, entwickelt. Im Gegensatz zu anderen Programmiersprachen ist es in Self möglich, den Zustand von Objekten zu vererben und die Vererbung zur Laufzeit dynamisch anzupassen.

Die letzte von Sun herausgegebene Version 4.3 wurde im Juni 2006 veröffentlicht. Sie läuft auf Intel- und PowerPC-basierten Apple-Rechnern und auf Sun SPARC, aber nicht unter Linux oder Microsoft Windows. Die Weiterentwicklung wird danach nicht mehr durch Sun betrieben und geschieht derzeit unabhängig.

Ideen und Konzepte, die ihren originären Ursprung in der Programmiersprache Self haben, wurden über die Jahre sowohl in das Squeak Smalltalk-System übernommen, als auch in Programmiersprachen wie Slate oder io weitergeführt.

Die herausragende Self-Eigenschaft ist wohl das Self-Universum, eine Art grafische Benutzeroberfläche (GUI), innerhalb derer mit dem Self-Laufzeitsystem interagiert werden kann. Eine Interaktion erfolgt hierbei in der Regel über die Maus und/oder die Tastatur. Visualisiert wird das Self-Universum mittels des GUI-Frameworks Morphic. Betritt eine Person das Self-Universum, so geschieht dies immer über die Lobby, eine Anspielung auf die Empfangshalle eines Hotels. In ihr befinden sich alle im System vorhandenen Objekte, Module, Namensräume und Traits. Das Konzept des (Programmier-)Universums als auch das Morphic-Konzept wurde nahezu identisch im Squeak Smalltalk-System umgesetzt.

Trotz der diversen Portierungen sollte man bedenken, dass es sich bei der Programmiersprache Self um ein eher akademisch motiviertes Unterfangen handelt. Trotzdem hatte und haben die in Self erprobten Neuheiten Einfluss auf neuere objektorientierte Programmiersprachen.

## Geschichte

Self wurde ursprünglich 1986 von David Ungar und Randall B. Smith während ihrer Arbeit am Xerox-Parc-Institut entworfen. Ihr Ziel war es, nachdem Smalltalk-80 veröffentlicht worden war, die Software-Technologie weiter voranzubringen. Sie wechselten zur Stanford-Universität und veröffentlichten im Folgejahr den ersten funktionierenden Compiler. Das erste vollständige Self-System wurde 1990 veröffentlicht; im Jahr darauf wechselte die Gruppe zu Sun Microsystems, wo das Self-System als Experimentierfeld für die Entwicklung genutzt wurde. Hier kam Urs Hölzle dazu, der neue Compiler- und VM-Techniken entwickelte und an der Entwicklung der Programmiersprache Strongtalk beteiligt war. 1995 wurde die Entwicklung bei Sun Microsystems offiziell eingestellt: „Self 4.0 ist, in einem gewissen Sinn, der Gipfelpunkt des Self-Projekts, das aber bei Sun offiziell nicht weitergeführt wird.“ Allerdings gab es noch die Entwicklergruppe an der Stanford-Universität sowie verschiedene andere Initiativen. Trotz des offiziellen Stops wurde im September 2002 die Version 4.1.6 veröffentlicht. Im April 2004 erschien dann die Version 4.2 und im Juni 2006 die Version 4.3. Seit Juli 2010 gibt es eine Version 4.4. Im Februar 2011 wurde ein experimenteller Snapshot einer Version 4.5 herausgegeben. Bis zur Veröffentlichung einer fertigen Version unter dem Namen "Mallard" dauerte es jedoch bis Januar 2014. Eine weitere Version mit dem Namen "Mandarin" entstand 2017.

## Die Entwicklungsumgebung

Das Self-System kann als prototypenbasierte Smalltalk-Variante verstanden werden. Die Entwickler bezeichnen die Sprache als Smalltalk-Dialekt und meinen damit, dass sich die Syntax der Sprache und auch die Objektbibliothek weitgehend an Smalltalk orientiert. Der Klassenbrowser von Smalltalk wurde durch einen Objektbrowser ersetzt.

Wie Smalltalk beruht Self auf einer Laufzeitumgebung und einer virtuellen Maschine. Self-Programme benötigen, genau wie andere auf virtuelle Maschinen beruhende Systeme, immer die zugehörige Speicherumgebung, um ausgeführt werden zu können. Als Folge werden Anwendungen häufig als Abbild des Speichers ausgeliefert. Dieses Speicherabbild wird auch Snapshot genannt.

Eine Alternative zur Auslieferung von Snapshots sind Textdateien, in die Änderungen am System oder auch ganze Module über den sogenannten Transporter ex- und importiert werden können. Diese Textdateien lassen sich mit einem beliebigen Editor bearbeiten. Sie sind vergleichbar mit einem C-Quelltext, jedoch wesentlich mächtiger. Das gesamte System lässt sich damit steuern. Da diese Textdateien auch automatisch mit dem Start des Systems eingelesen werden können, kann Self genau wie Perl als Skriptsprache genutzt werden.

Die typische Größe eines Snapshots ist ca. 13 MB für ein vollständiges System mit JIT-Compiler, Interpreter und der Entwicklungsumgebung. Der von Smalltalk geerbte Vorteil ist, dass die Arbeit am System jederzeit, auch mitten in einer Berechnung, unterbrochen werden und in Snapshots gespeichert werden kann. Alle Änderungen, also das was bei klassischen Systemen dem Programmieren, einer Zeichnung oder irgendeiner Arbeit mit dem System entspricht, können beim nächsten Neustart auf Wunsch wieder vorhanden sein. Das System wacht da auf, wo es verlassen wurde. Self ist persistent.

Die Self-Entwicklungsumgebung ermöglicht es Quellcode, Objekte, Vererbung, die Objekthierarchie oder einzelne Werte zur Laufzeit zu verändern. Möglich wird das durch die Fähigkeit zum Multitasking und einen nahtlos in das System integrierten Debugger. Das führt zu einer inkrementellen Art des „on the fly“-Entwickelns. Einige moderne Methoden der Softwareentwicklung, etwa die Agile Softwareentwicklung oder das Adaptive Software Development, propagieren Ähnliches.

So ist die Entwicklungsumgebung auf das schnelle und kontinuierliche Ändern einzelner Objekte zugeschnitten. Das Refactoring des „Objekt“-Designs ist beinahe so einfach wie das Entfernen einer Methode via Drag & Drop, um sie so einem neuen Objekt zuordnen zu können. Einfachere Aufgaben wie das Erstellen von Test-Methoden können durch eine Kopie bewältigt werden. Die entsprechende Methode wird anschließend in die Objekt-Kopie gezogen, um sie so verändern zu können. Im Vergleich zu traditionellen Systemen besitzt nur das soeben neu erzeugte Objekt auch neuen Quellcode. Es ist folglich nicht erforderlich, Code neu zu kompilieren, um ihn testen zu können. Sobald sichergestellt wurde, dass die Test-Methode entsprechend der Spezifikation funktioniert, kann diese zurück in das Ausgangs-Objekt kopiert werden.

## Sprachbeschreibung

Im Folgenden soll nur ein erster Eindruck dieser Programmiersprache vermittelt werden. Für umfassende Informationen sei auf The Self Language und Teaching Self and Prototype-Based Programming verwiesen.

Self ist eine objektorientierte, prototypenbasierte Programmiersprache. Die Grundlage bilden einige wenige einfache Konstrukte: Prototypen, Steckplätze und das Verhalten. Im Gegensatz zu den Konzepten von Smalltalk und vielen anderen Programmiersprachen gibt es weder Klassen noch Variablen. Das Programmiermodell beruht im Wesentlichen auf kommunizierenden Objekten und einer einfachen Form von Vererbung.

In Self werden keine Programme im eigentlichen Sinn geschrieben – stattdessen wird das „Self-Universum“ beschrieben. Hier gibt es nur Objekte, die durch das Verschicken von Nachrichten miteinander kommunizieren. Dieses Verschicken einer Nachricht entspricht einem Prozedur-Aufruf in der klassischen Programmierung. Neue Objekte werden durch Klonen bestehender Objekte erzeugt.

Durch das Prototypen-Konzept ist eine interaktive Programmierung möglich: Der Programmierer interagiert mit den Objekten im Self-Universum.

In Self wird nicht zwischen dem Zustand eines Objekts und seinem Verhalten unterschieden. Der Zustand eines Objekts ergibt sich aus seinen Eigenschaften, also den Werten seiner Attribute. Das Verhalten eines Objekts wird in seinen Methoden beschrieben. Dadurch, dass es keinen Unterschied zwischen dem Zustand und dem Verhalten gibt, wird die Lücke verringert, die in anderen Programmiersprachen zwischen Objekten, Prozeduren und Variablen besteht. In Self ist alles eins.

So liefert beispielsweise der Aufruf einer Eigenschaft – sofern das Objekt oder eines seiner Vorfahren diese Eigenschaft denn hat – deren Wert:

In dieser Anweisung wird die Eigenschaft name des Objekts aPerson aufgerufen bzw. abgefragt. Mit der folgenden Anweisung wird derselben Eigenschaft ein Wert zugewiesen:

### Sprachkonstrukte

Objekte bestehen aus (theoretisch) beliebig vielen Steckplätzen (slots). Ein Steckplatz hat einen Namen und kann ein Attribut (Wert) oder eine Methode (Prozedur) enthalten. In jedem Fall ist es ein Objekt, das er aufnimmt. Ein Objekt besteht somit aus einem oder mehreren Steckplätzen für Objekte, auf die mittels des Namens zugegriffen werden kann.

Ein Methoden-Objekt enthält eine oder mehrere Anweisungen, die ausgeführt werden, wenn das Methoden-Objekt aufgerufen wird. Ein Attribut-Objekt liefert sich selbst zurück, wenn es wie ein Methoden-Objekt aufgerufen wird.

Neue Objekte werden durch Klonen erzeugt. Dazu wird ein passendes Objekt als Prototyp benutzt, also sozusagen als eine Kopiervorlage. Jedes Objekt kann als Prototyp genutzt werden.

## Ein kurzes Programm

Hier das klassische Hallo-Welt-Programm:

Der Punkt am Ende der Anweisung bewirkt, dass die Rückgabe des Wertes – die eigentlich bei allen Nachrichten erfolgt – unterdrückt bzw. vermieden wird. Hier noch einmal in einer aufwändigeren Form:

Erläuterung: Als erstes wird das desktop-Objekt nach dem aktiven Fenster gefragt. Das desktop-Objekt sieht dazu in seiner Liste der aktuellen Fenster nach und liefert das entsprechende Objekt zurück. Diesem Objekt wird in dem Steckplatz namens draw eine zuvor erzeugte Kopie des labelWidget-Objekt eingetragen. Bevor das passiert, wurde der labelWidget-Kopie bereits die Nachricht geschickt, dass es einen Steckplatz mit dem Namen label anlegen soll und dort den Wert Hello, World! ablegen soll.

## Vererbung

Theoretisch ist jedes Self-Objekt eine eigenständige Entität. Es existieren keine Klassen, keine Meta-Klassen etc., um die Eigenschaften und Verhaltensweisen eines Objektes zu spezifizieren. Änderungen am Objekt selbst beeinflussen das Verhalten anderer Objekte nicht. In Einzelfällen jedoch wäre dies durchaus sinnvoll. Grundsätzlich versteht ein Objekt nur die Nachrichten, die auch einem seiner Slots eindeutig zugeordnet werden können. Verweisen jedoch ein oder mehrere Slots auf ein parent-Objekt, so ist das Objekt imstande, Nachrichten an diese Elternobjekte zu delegieren, insofern es diese selbst nicht zu interpretieren vermag. Auf diesem Weg verwaltet Self Aufgaben, die in traditionellen objektorientierten Sprachen das Vehikel der Vererbung benötigt hätten. Diese Verfahrensweise wird zudem benutzt, um Namensräume zu implementieren.

Um den Zusammenhang der Vererbung besser veranschaulichen zu können, soll an dieser Stelle von einem Objekt namens BankKonto ausgegangen werden. Dieses soll in einer einfachen Buchhaltungsanwendung zum Einsatz kommen. Es liegt nahe, das Objekt BankKonto mit den Methoden einzahlen und abheben auszustatten. Zusätzliche Slots für einfache Daten werden ebenfalls benötigt. Das soeben spezifizierte Objekt ist ein Prototyp, stellt aber bereits ein voll funktionsfähiges Bankkonto dar.

Erstellt man einen Klon des Objekts BankKonto für „Bobs Konto“, so erhält man eine Kopie, die mit dem ursprünglichen Prototyp identisch ist. Eine effektivere Herangehensweise ist es jedoch, ein einfaches Objekt namens traits object zu erstellen, das all die Elemente enthält, die man normalerweise mit einer Klasse assoziieren würde.

Analog zum aktuellen Beispiel, würde das Objekt BankKonto als logische Konsequenz weder eine einzahlen- noch eine auszahlen-Methode besitzen. Vielmehr hat es nun ein Elternobjekt, das diese Methoden enthält. Auf diese Weise ist es möglich, unzählige Kopien des Bankkonto-Objekts zu erzeugen, deren Verhalten durch eine einzige Änderung am ElternObjekt verändert werden können.

Wie unterscheidet sich der geschilderte Sachverhalt von den traditionellen Klassen einer OO-Sprache? Was bedeutet wohl das nachfolgende Konstrukt:

Dieses Konstrukt verändert die „Klasse“ von myObject zur Laufzeit, indem der Wert, der mit dem Slot parent* assoziiert ist, verändert wird (der Stern ist Teil des Slot-Namens und gehört folglich nicht zur Nachricht, die dieser Slot empfängt).

## Slots hinzufügen

An dieser Stelle stellt sich die Frage, wie Kopien eines Self-Objekts modifiziert werden müssen, damit diese neue Slots enthalten. Benutzt man die grafische Self-Programmierumgebung, gestaltet sich dies ziemlich einfach. Programmatisch gesehen ist es am sinnvollsten, ein sogenanntes Mirror-Objekt des Objekts zu erzeugen und dann zu modifizieren. Dazu schickt man diesem Mirror-Objekt entsprechende Nachrichten.

Ein Weg, der schneller zum Ziel führt, ist den Primitiv _AddSlots: zu verwenden. Ein Primitiv hat dieselbe Syntax wie eine normale Schlüsselwort-Nachricht. Jedoch beginnt dessen Name mit einem Unterstrich. Der _AddSlots-Primitiv gilt inzwischen als überholt; wegen seiner Kürze wird er hier trotzdem verwendet.

Eines der ersten Beispiele beschäftigte sich mit dem Refactoring einer einfachen Klasse namens Fahrzeug. Das Refactoring wurde notwendig, um zwischen Personen- und Lastkraftwagen unterscheiden zu können. Versucht man das Refactoring in Self umzusetzen, so sieht dies etwa aus wie folgt:

Da der Empfänger des _AddSlots:-Primitivs nicht angegeben wurde, ist dieser automatisch „self“. Wird ein Ausdruck innerhalb des Shell-Prompt der Entwicklungsumgebung eingegeben und ausgeführt, so ist das Objekt, das daraus resultierende Nachrichten empfängt, immer das lobby-Objekt. Das an _AddSlots: übergebene Argument ist das Objekt, das in das Empfänger-Objekt kopiert wird. In diesem Fall handelt es sich um ein Objekt-Literal mit genau einem Slot. Der Name des Slots ist vehicle, und dessen Wert ist ein weiteres Objekt-Literal. Die Notationsweise „<-“ impliziert einen weiteren Slot namens vehicle:, der notwendig ist, um den Wert des ersten Slots zu verändern.

Das „=“ weist auf einen Slot mit konstantem Wert hin. Aus diesem Grund gibt es auch keinen korrespondierenden parent: Slot. Das Objekt-Literal, das den Initialwert vehicle repräsentiert, hat einen Slot, um auf Nachrichten des Klonens reagieren zu können. Ein Objekt, das tatsächlich leer ist, wird durch (||) oder einfach dargestellt. Ein solches Objekt ist nicht in der Lage, Nachrichten zu empfangen.

In diesem Beispiel ist der Nachrichten-Empfänger das vorherige Objekt, das zusätzlich zum parent*-Slot sowohl einen name- als auch einen name:-Slot enthält.

Trotz der Tatsache, dass es sich zuvor bei den Objekten vehicle und sportsCar um identische Objekte handelte, beinhaltet das aktuelle Objekt nun einen Slot, dem eine Methode zugeordnet ist, die es zuvor nicht gab. Methoden können nur in Slots mit konstantem Wert enthalten sein.

## Versionen

* 4.0: läuft auf SPARC-basierten Sun-Workstations mit SunOS 4.1.x, Solaris 2.3, oder Solaris 2.4 als Betriebssystem
* 4.1.6: läuft auch als Anwendung unter Mac OS X. Die Unterstützung für Mac OS 9 wurde eingestellt
* 4.2.1: läuft auch auf G3-Macs
* 4.3: läuft auch auf Intel-basierten Macs
* 4.4: läuft unter Mac OS X und Linux (x86)
* 4.5: läuft unter Mac OS X und Linux
