Self (Programmiersprache)
Self
| |
---|---|
![]() | |
Basisdaten
| |
Entwickler | Sun |
Erscheinungsjahr | 1987 |
Aktuelle Version | 4.2.1 |
Betriebssystem | Windows, Linux, Mac OS X, Sun Solaris. |
Kategorie | objektorientierte Programmiersprache |
Lizenz | implementationsspezifisch |
deutschsprachig | ja |
selflanguage.org |

Self ist eine objektorientierte, dynamisch typisierte Programmiersprache. Diverse Quellen bezeichnen Self als einen Smalltalk-Dialekt, was wohl auf die nahezu identische Sprachsyntax zurückzuführen ist. Dennoch unterscheidet sich Self in einem wesentlichen Punkt im direkten Vergleich mit Smalltalk. Self ist eine prototypische Programmiersprache ohne Klassendefinitionen. Folglich ist ein Self-Objekt nicht die Manifestation einer Klasse im Sinne eines Bauplans, sondern direktes Resultat eines Prototyps.
Ein Prototyp besteht aus so genannten Slots. Diese können sowohl eine Eigenschaft, wie beispielsweise einen numerische Wert, aber auch eine Methode repräsentieren, welche ihrerseits in der Lage ist Nachrichten zu empfangen.
Self Historie
Self wurde in den 1990er-Jahren an den Sun Microsystems Laboratories, Inc. entwickelt. Federführend wurde die Entwicklung von Urs Hoelzle und David Ungar vorangetrieben. Während dieser Zeit wurde die Programmiersprache vor allem als Experimentierfeld für die Entwicklung von Sprachen genutzt. Aus dieser Periode stammt die aktuelle Version 4.x welche unlängst im April 2004 auf die Version 4.2.1 aktualisiert wurde. Sie ist jeweils für die Plattform PowerPC (Mac OS X) und SPARC (Solaris) als Download verfügbar.
Richtet man seinen Blick auf neuere Sprachen wie beispielsweise Java, so sollte man sich der Tatsache bewusst werden, dass in diese diverse Self-Entwicklungen einflossen. Eine wesentliche Technologie die an dieser Stelle genannt werden muss, ist die von Urs Hoelzle entwickelte Compiler- und VM-Technologie. Sie ist Grundlage für die heutigen Java Hot-Spot- bzw. adaptive Compiler-Technologie. Urs Hoelzle war im Übrigen auch an der Entwicklung der Strongtalk Programmiersprache beteiligt.
Vergegenwärtigt man sich letztlich das Konzept der Vererbung, wie es in JavaScript umgesetzt wurde, so wird klar, dass es auch dort nur Objekte aber keine Klassenschablonen gibt. Jedes JavaScript-Objekt ist folglich ein prototypisches Objekt - Auch dieser Umstand legt nahe, dass hier das eine oder andere Self-Konzept entliehen wurde.

Self heute
Ideen und Konzepte die ihren originären Ursprung in der Self Programmiersprache haben, wurden über die Jahre sowohl in das Squeak Smalltalk-System übernommen, als auch in Programmiersprachen wie Slate oder io weitergeführt. Herausragendste Self-Eigenschaft ist wohl das Self-Universum, eine Art grafische Benutzeroberfläche innerhalb derer mit dem Self-Laufzeitsystem interagiert werden kann. Eine Interaktion erfolgt hierbei in der Regel über die Maus und/oder die Tastatur. Dieses Universum ist als Metapher auf die reale Welt, in der wir leben, zu verstehen und lässt Parallelen zu Platons Höhlengleichnis erkennen.
Visualisiert wird das Self-Universum mittels eines GUI-Frameworks namens Morphic. Betritt eine Person das Self-Universum, so geschieht dies immer über die Lobby, wahrscheinlich 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.
Zusätzlich zu der ursprünglichen Self-Implementierung, existieren heute auch Portierungen für x86-basierte Systeme. Trotz der diversen Portierungen, sollte man sich dringlichst vor Augen führen, dass es sich bei der Self Programmiersprache um ein eher akademisch motiviertes Unterfangen handelt. Trotzdem hatte und haben die in Self erprobten Neuheiten, Einfluss auf neuere objektorientierte Programmiersprachen.
Problemstellung
Traditionell basieren objektorientierte Sprachen auf einem tief verwurzelten Dualismus. Klassen dienen als Blaupause und spezifizieren die grundlegenden Eigenschaften und Verhaltensweisen eines Objekts. Eine Instanz wiederum ist eine spezielle Ausprägung eines Objekts, basierend auf der Spezifikation wie sie in einer Klasse enthalten ist.
So könnte man sich beispielsweise die Klasse Fahrzeug
, welche die Eigenschaft "name" und die Operationen "fahreZurArbeit" und "liefereBauMaterial" besitzt, vorstellen. Porsche 911
wäre folglich eine spezifische Instanz der Klasse Fahrzeug
mit der Namens-Eigenschaft "Porsche 911". Ob der geschilderten Klassenspezifikation ist es nun theoretisch möglich, der Objektinstanz Porsche 911
die Nachricht mit dem Inhalt "liefereBauMaterial" zukommen zu lassen.
Das geschilderte Beispiel veranschaulicht das Problem, welches diesem Lösungsansatz zugrundeliegt. Ein Porsche ist grundsätzlich per-se nicht in der Lage Baumaterialien auszuliefern. Ein Fahrzeug hingegen, wäre grundsätzlich in der Lage Baumaterialien auszuliefern. Eine gängige Methodik um ein solchen Problem zu vermeiden, ist das Ergänzen der Klasse Fahrzeug um eine Spezialisierung. Dies geschieht durch das Erzeugen einer Unterklasse. Eine Klasse des Typs "SportFahrzeug" und "PritschenWagen" wäre beispielsweise vorstellbar.
Zugegeben, es handelt sich hierbei um ein etwas gekünsteltes Beispiel. Dennoch wird das eigentliche Problem sichtbar - Insofern es nicht absolut sicher gestellt ist, welche Ausprägung ein bestimmtes Objekt zukünftig einnimmt, ist es nicht möglich, eine fehlerfreie und umfassende Klassenhierarchie zu spezifizieren. Allzu oft entspricht es der Realität, dass sich die Anforderungen an ein Softwaresystem stetig weiterentwickeln. Dies hat zur Folge, das selbiges kontinuierlich angepasst oder refaktoriert werden muss.
Erfahrungen mit frühen Programmiersprachen, wie sie beispielsweise mit Smalltalk gewonnen werden konnten, haben gezeigt, dass Probleme resultierend aus dem stetigen Wandel eines Softwaresystems alltäglich sind. Ein solches System wächst bis zu einem bestimmten Punkt und verhält sich dann aber sehr unflexibel, sodass Änderungen kaum möglich sind. Zudem reflektieren die grundlegenden Klassen eines solchen Systems nicht mehr die Realität und sind folglich in ihrer Semantik schlichtweg "falsch". Ohne die Möglichkeit die ursprünglichen Klassen in ihrer Funktion sehr schnell ändern zu können, kommt es unweigerlich auf kurz oder lang zu erheblichen Problemen.
Dynamisch typisierte Sprachen wie Smalltalk erlauben diese Art der Änderung mittels namhafter Methoden einer Klasse. Eine Änderung der Klasse resultiert automatisch auch in einer Veränderung des Verhaltens der Objekte, die auf dieser Klasse basieren. In anderen Sprachen wie beispielsweise C++ gibt es diese Möglichkeit hingegen nicht, was zu einer Störung des Softwaresystems führen kann. Diese Problem ist auch als "Fragile base class" Problem bekannt. Grundsätzlich müssen Änderungen an einem solchen System auf jeden Fall mit Bedacht vorgenommen werden.

Problemlösungsansatz
Wie zuvor beschrieben, besteht das Problem in einem grundsätzlichen Dualismus von Klasse und Klasseninstanz. Self besitzt diesen Dualismus aufgrund seiner prototypischen Natur schlichtweg nicht.
Die Stelle der "Instanz" eines Objekts basierend auf einer "Klasse", nimmt in Self die Kopie eines bereits existierenden Objekts ein. Eine solche Kopie wird folglich auf die aktuellen Bedürfnisse angepasst. So würde zum Beispiel der "Porsche 911" durch das Kopieren des bereits existierenden "Fahrzeug" Objekts erzeugt werden. Anschließend muss dem "Porsche 911" Objekt nur noch die fehlende "fahreZurArbeit" Methode hinzugefügt werden. Primitive Objekte die benötigt werden um Objekt-Kopien zu erzeugen, werden auch als Prototypen bezeichnet.
Dies hört sich nicht gerade nach einer bahnbrechenden Errungenschaft an, jedoch kann so der Dynamik eines Softwaresystems erheblich entgegengewirkt werden. Ist es beispielsweise notwendig, ein Problem in einer "Basisklasse" zu beheben, so ist es in einem Self System möglich, diese zu ändern, um so zukünftig von dem soeben neu erzeugten Objekt Kopien erstellen zu können. Sollte es folglich zukünftig möglich sein, dass ein Porsche auch Baumaterialien ausliefert, so kann diese Eigenschaft dem existierenden Prototyp einfach hinzugefügt werden.
Zudem vereinfacht diese Eigenschaft eines Self Systems das Konzept der Objektorientierung um ein Vielfaches. Jedes Subjekt in einer traditionellen Programmiersprache mag ein Objekt sein, aber die Unterscheidung zwischen Klasse und Objektinstanz existiert weiterhin. Diese Unterscheidung gibt es in Self nicht.
Die Sprache
Self-Objekte sind eine Ansammlung von "Slots". Slots sind mit C# Properties oder den Java Getter/Setter Methoden vergleichbar. Ein Slot liefert folglich einen Wert oder setzt diesen, insofern dieser mit einem zusätzlich Doppelpunkt versehen ist. Existiert beispielsweise ein Slot namens "name", so liefert nachfolgendes Konstrukt den Wert der Eigenschaft "name"
myPerson name
und nachfolgendes Konstrukt würde hingegen den Wert der Eigenschaft "name" setzen
myPerson name:'gizifa'
Self benutzt, wie auch Smalltalk, Blöcke um den Programmfluss zu steuern. Methoden entsprechen Objekten die Quellcode enthalten, um so die eigentlichen Slots zu ergänzen. Sie können innerhalb eines Self-Slots, wie jedes andere Objekt auch, platziert werden.
In Self wird nicht zwischen Feldern oder Methoden unterschieden. Grundsätzlich wird alles als Slot behandelt. Da die grundlegende Idee in einem Self System auf der Weitergabe von Nachrichten beruht, ist es auch nicht verwunderlich, dass vielfach ein Nachricht an "self" geschickt wird. Das "self" ist, wie auch in anderen Sprachen mit dem selben Sprachkonstrukt, jedoch nicht zwingen erforderlich.

Sprachsyntax
Die Syntax um einen Slot anzusprechen, ist mit der Smalltalk-Syntax vergleichbar.
- Unäre Syntax
receiver slot_name
- Binäre Syntax
receiver + argument
- Schlüsselwort
receiver keyword: arg1 With: arg2
Sämtliche Nachrichten liefern ein Ergebnis zurück. So kann der Empfänger einer Nachricht (insofern vorhanden, ansonsten "self") als auch ein Argument, die Rolle einer Nachricht einnehmen. Besitzt eine Nachricht eine Punkt als Suffix, so bedeutet dies, dass der Rückgabewert ohne Bedeutung ist und folglich unterschlagen wird. Hier ein Beispiel:
'Hello, World!' print.
Dies ist die Self-Variante des klassischen hello world Programms. Die '
Schreibweise repräsentiert einen String-Literal. Andere Literale sind Nummern, Blöcke oder die eigentlichen Objekte.
Gruppierungen erfolgen, wie in der Mathematik, durch das Setzen von runden Klammern. Fehlen diese, so hat die unäre Nachricht Vorrang. Sie wird vor den binären Nachrichten evaluiert und ausgeführt. Schlüsselwörter werden hingegen zuletzt mit der niedrigsten Priorität ausgeführt. Werden Schlüsselwörter für Zuweisungen verwendet, so hat dies womögliche eine zusätzliche Klammerung an diesen Stellen zur Folge. Um dies zu vermeiden wird in Self der erste Teil eines Schlüsselwortes klein geschrieben, der verbleibende Rest hingegen wird gross geschrieben. Hier ein Beispiel zu diesem Sachverhalt:
valid: base bottom between: ligature bottom + height And: base top / scale factor.
Dies würde exakt dem nachfolgenden Konstrukt entsprechen:
valid: ((base bottom) between: ((ligature bottom) + height) And: ((base top) / (scale factor))).
In einem Smalltalk-80 System wäre die äquivalente Schreibweise wie folgt:
valid := self base bottom between: self ligature bottom + self height and: self base top / self scale factor.
Neue Objekte erzeugen
Ausgehend von den vorherigen Beispielen hier nun ein etwas komplexeres Beispiel:
labelWidget copy label: 'Hello, World!'.
Diese Beispiel erzeugt eine Kopie des "labelWidget" Objektes, indem diesem die Nachricht "copy" geschickt wird. Anschließend wird dem neu erzeugten Objekt eine Nachricht geschickt, die den Slot mit dem Namen "label" mit dem Wert "Hello, World" initialisiert. Das so erzeugte Objekt lässt sich nun weiter verwenden:
(desktop activeWindow) draw: (labelWidget copy label: 'Hello, World!').
In diesem Fall wird (desktop activeWindow)
ausgeführt. Dies resultiert in einer Liste aller aktiven Fenster die dem Objekt Desktop aktuell bekannt sind. Als nächstes (Leserichtung: von Innen nach Außen, von rechts nach links) wird das labelWidget zurückgegeben. Schließlich wird das Widget an den draw Slot des aktiven Fensters geschickt.
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. Verweist jedoch ein oder mehrere Slots auf ein parent Objekt, so ist das Objekt imstande etwaige Nachrichten an diese Eltern-Objekte zu delegieren, insofern es diese selbst nicht zu interpretieren vermag. Auf diesem Weg verwaltet Self Aufgaben, die in traditionellen OO-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ätzlich Slots für einfache Daten werden ebenfalls benötigt. Das soeben spezifizierte Objekt stellt einen vollwertigen Prototyp dar. Er ist nur insofern besonders als das er bereits einen voll funktionsfähiges Bankkonto darstellt.
Erstellt man einen Klon des "BankKonto" Objekts für "Bob's 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, welches all die Elemente enthält die man normalerweise mit einer Klasse assoziieren würde.
Analog zum aktuellen Beispiel, würde das "BankKonto" Objekt als logische Konsequenz weder eine "einzahlen" noch eine "auszahlen" Methode besitzen. Vielmehr hat das "BankKonto" Objekt nun ein Eltern-Objekt, welches die genannten 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 Eltern-Objekt verändert werden können.
Wie unterscheidet sich der geschilderte Sachverhalt von den traditionellen Klassen einer OO-Sprache? Was bedeutet wohl das nachfolgende Konstrukt:
myObject parent: someOtherObject.
Dieses Konstrukt ist durchaus von Interesse. Dieses Konstrukt verändert die "Klasse" von myObject zur Laufzeit, indem der Wert der mit dem 'parent*' Slot assoziiert ist, verändert wird (der Stern ist Teil des Slot-Namens und gehört folglich nicht zur Nachricht, die zu diesem Slot gehört).
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 diese ziemlich einfach. Programmatisch gesehen, ist es am sinnvollsten eine so genanntes Mirror-Objekt des Objekts zu erzeugen, das letztlich modifiziert werden soll. Diesem Mirror-Objekt schickt man nun diverse Nachrichten um es entsprechend zu modifizieren.
Ein Weg der schneller zum Ziel führt ist es, 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 sollte eigentlich vermieden werden, da er eine Hinterlassenschaft früherer Self-Versionen ist. Trotzdem wird er hier verwendet, da er das nachfolgende Quellcode-Beispiel nicht so sehr auf bläht.
Eines der ersten Beispiele beschäftigte sich mit dem Refactoring einer einfachen Klasse namens Fahrzeug. Das Refactoring wurde notwendig um zwischen Autos und Lastkraftwagen unterscheiden zu können. Versucht man das Refactoring in Self umzusetzen, so hat dies ungefähr wie folgt zu erfolgen:
_AddSlots: (| vehicle <- (|parent* = traits clonable|) |).
Da der Empfänger des '_AddSlots:' Primitivs nicht angegeben wurde, ist dieser automatisch "self". Wird ein Ausdruck innerhalb des Shell-Prompt des Betriebssystems eingegeben und ausgeführt, so ist das Objekt welches daraus resultierende Nachrichten empfängt, immer das "lobby" Objekt. Das Argument, welches an '_AddSlots:' übergeben wird, 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 welches den Initialwert 'vehicel' 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.
vehicle _AddSlots: (| name <- 'automobile'|).
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.
_AddSlots: (| sportsCar <- vehicle copy |). sportsCar _AddSlots: (| driveToWork = (some code, this is a method) |).
Trotz der Tatsache, dass es sich zuvor bei den Objekten 'vehicle' und 'sportsCar' um identische Objekt handelte, beinhaltet das aktuelle Objekt nun einen Slot, dem eine Methode zugeordnet ist, die es zuvor nicht gab. Methoden können übrigens nur in Slots mit konstantem Wert enthalten sein.
_AddSlots: (| porsche911 <- sportsCar copy |). porsche911 name:'Bobs Porsche'.
Die Entwicklungsumgebung
Eine der wenigen Einschränkungen, die die Self-Entwicklungsumgebungen mit sich bringt, ist die Tatsache, dass die Laufzeitumgebung auf einer virtuellen Maschine basiert, die starke Ähnlichkeit mit den VMs früher Smalltalk Systeme hat. Daraus resultiert das Fakt, das Programme keine eigenständige Entität darstellen, wie dies zum Beispiel bei C Programm der Fall ist. Self-Programme benötigen immer die zugehörige Speicher-Umgebung um ausgeführt werden zu können. Als Folge müssen Anwendungen als Abbild des Speichers ausgeliefert werden. Dieses Speicherabbild wird auch Snapshot genannt. Snapshots sind oftmals sehr gross und umständlich zu benutzen.
Auf der anderen Seite ist die Self-Entwicklungsumgebung sehr mächtig. Programme können zu jedem Zeitpunkt unterbrochen werden, um diverse Werte oder ein Stück Code zu ändern. Das Programm lässt sich anschließend genau an der Stelle fortsetzen, an der es zuvor unterbrochen wurde. Diese Art des "on the fly" Entwickelns wirkt sich sehr positiv auf die Produktivität aus.
Zusätzlich ist die Entwicklungsumgebung auf das schnelle und kontinuierliche Ändern einzelner Objekte zugeschnitten. Das Refactoring des "Klassen"-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 System, 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.
Liste wichtiger Self-Implementierungen
- http://research.sun.com/self/ - Die orginäre Self-Implementierung von Sun
- http://www.self-support.com/ - x86 Self-Implementierung für Linux und Cygwin
- http://flp.cs.tu-berlin.de/~tolk/dself/ - Distributed Self
Literatur
- Urs Hölzle, David Ungar: Reconciling Responsiveness with Performance in Pure Object-Oriented Languages., in: Proceedings of the ACM OOPSLA `94 Conference, Portland, OR, October 1994, http://citeseer.csail.mit.edu/412267.html
- Tim Lindholm, Frank Yellin: The Java Virtual Machine Specification. 2. Auflage. Addison-Wesley Professional, 1. April 1999. ISBN 0201432943
Weblinks
- http://www.smalltalk.org.br/movies/ - Self: The Movie (Quicktime 81 MByte)
- http://www.merlintec.com:8080/Self/1 - Self Wiki
- Sehr lesenswerte Informationen zur Strongtalk-Historie
- http://www.cs.ucsb.edu/labs/oocsb/self/papers/papers.html - Papers on Self from UCSB (mirror for the Sun papers page)
- http://www.cetus-links.org/oo_self.html - Self resources at Cetus Links
- http://groups.yahoo.com/group/self-interest/ - Yahoo! Gruppe über Self
- http://www.merlintec.com/lsi/ - Merlin Projektseite
- http://selfguru.sourceforge.net/ - Refactoring Anwendung für Self