Selbst (Programmiersprache) - Self (programming language)

Selbst
Logo
Paradigma objektorientiert ( prototypbasiert )
Entworfen von David Ungar , Randall Smith
Entwickler David Ungar, Randall Smith, Stanford University , Sun Microsystems
Erstmals erschienen 1987 ; Vor 34 Jahren ( 1987 )
Stabile Version
Mandarin 2017.1 / 24. Mai 2017 ; vor 4 Jahren ( 2017-05-24 )
Schreibdisziplin dynamisch , stark
Lizenz BSD-ähnliche Lizenz
Webseite www .selflanguage .org
Wichtige Implementierungen
Selbst
Beeinflusst von
Smalltalk , APL
Beeinflusst
NewtonScript , JavaScript , Io , Agora , Squeak , Lua , Faktor , REBOL

Self ist eine objektorientierte Programmiersprache , die auf dem Konzept von Prototypen basiert . Self begann als Dialekt von Smalltalk , wurde dynamisch typisiert und nutzte Just-in-Time-Kompilierung (JIT) sowie den prototypbasierten Zugang zu Objekten: Es wurde erstmals in den 1980er und 1990er Jahren als experimentelles Testsystem für Sprachdesign verwendet . Im Jahr 2006 wurde Self noch im Rahmen des Klein-Projekts entwickelt, einer virtuellen Self-Maschine, die vollständig in Self geschrieben wurde. Die neueste Version ist 2017.1 und wurde im Mai 2017 veröffentlicht.

In der Self-Forschung wurden mehrere Just-in-Time-Kompilierungstechniken entwickelt und verbessert, da sie erforderlich waren, um eine sehr hohe objektorientierte Sprache mit bis zu halber Geschwindigkeit von optimiertem C zu ermöglichen. Ein Großteil der Entwicklung von Self fand bei Sun . statt Microsystems und die Techniken , die sie entwickelt wurden später für bereitgestellte Java ‚s HotSpot Virtual Machine .

Irgendwann wurde eine Version von Smalltalk in Self implementiert. Da es das JIT nutzen konnte, ergab dies auch eine extrem gute Leistung.

Geschichte

Self wurde 1986 hauptsächlich von David Ungar und Randall Smith entworfen, als sie bei Xerox PARC arbeiteten . Ihr Ziel war es, den Stand der Technik in der objektorientierten Programmiersprachenforschung voranzutreiben, sobald Smalltalk-80 von den Labors veröffentlicht und von der Industrie ernst genommen wurde. Sie zogen an die Stanford University und setzten die Arbeit an der Sprache fort und bauten 1987 den ersten funktionierenden Self-Compiler. An diesem Punkt verlagerte sich der Fokus auf den Versuch, ein ganzes System für Self zu entwickeln, anstatt nur die Sprache.

Die erste öffentliche Veröffentlichung erfolgte 1990, und im nächsten Jahr wechselte das Team zu Sun Microsystems, wo die Arbeit an der Sprache fortgesetzt wurde. Es folgten mehrere neue Releases, bis sie 1995 mit der Version 4.0 weitgehend inaktiv wurden. Die Version 4.3 wurde 2006 veröffentlicht und lief auf Mac OS X und Solaris . Eine neue Version im Jahr 2010, Version 4.4, wurde von einer Gruppe entwickelt, die aus einigen des ursprünglichen Teams und unabhängigen Programmierern besteht und ist für Mac OS X und Linux verfügbar , wie auch für alle folgenden Versionen. Das Follow-up 4.5 wurde im Januar 2014 veröffentlicht und drei Jahre später wurde die Version 2017.1 im Mai 2017 veröffentlicht.

Self inspirierte auch eine Reihe von Sprachen, die auf seinen Konzepten basieren. Am bemerkenswertesten waren vielleicht NewtonScript für den Apple Newton und JavaScript, das in allen modernen Browsern verwendet wird. Andere Beispiele sind Io , Lisaac und Agora . Das 1990 entwickelte verteilte Objektsystem des IBM Tivoli Framework war auf der untersten Ebene ein von Self inspiriertes prototypbasiertes Objektsystem.

Prototypbasierte Programmiersprachen

Traditionelle klassenbasierte OO-Sprachen basieren auf einer tief verwurzelten Dualität:

  1. Klassen definieren die grundlegenden Eigenschaften und Verhaltensweisen von Objekten.
  2. Objektinstanzen sind besondere Manifestationen einer Klasse.

Angenommen, Objekte der VehicleKlasse haben einen Namen und die Fähigkeit, verschiedene Aktionen auszuführen, wie z. B. zur Arbeit fahren und Baumaterial liefern . Bob's carist ein besonderes Objekt (Instanz) der Klasse Vehicle, mit dem Namen "Bobs Auto". Theoretisch kann man dann eine Nachricht an schicken Bob's car, um Baumaterial zu liefern .

Dieses Beispiel zeigt eines der Probleme bei diesem Ansatz: Bobs Auto, das zufällig ein Sportwagen ist, ist nicht in der Lage, Baumaterialien (in einem sinnvollen Sinne) zu transportieren und zu liefern, aber dies ist eine Fähigkeit, die den VehicleModellen nachempfunden ist. Ein nützlicheres Modell ergibt sich aus der Verwendung von Unterklassen , um Spezialisierungen von Vehicle; zum Beispiel Sports Carund Flatbed Truck. Nur Objekte der Klasse Flatbed Truckmüssen einen Mechanismus bereitstellen, um Baumaterialien zu liefern ; Sportwagen, die für diese Art von Arbeit nicht geeignet sind, müssen nur schnell fahren . Dieses tiefere Modell erfordert jedoch mehr Einsichten während des Entwurfs, Einsichten, die möglicherweise erst ans Licht kommen, wenn Probleme auftreten.

Dieses Thema ist einer der Motivationsfaktoren für Prototypen . Wenn man nicht mit Sicherheit vorhersagen kann, welche Qualitäten eine Menge von Objekten und Klassen in ferner Zukunft haben werden, kann man eine Klassenhierarchie nicht richtig entwerfen. Allzu oft brauchte das Programm schließlich zusätzliche Verhaltensweisen, und Teile des Systems müssten neu gestaltet (oder umgestaltet ) werden, um die Objekte auf andere Weise herauszulösen. Erfahrungen mit frühen OO-Sprachen wie Smalltalk haben gezeigt, dass diese Art von Problemen immer wieder auftaucht. Systeme würden dazu neigen, bis zu einem gewissen Punkt zu wachsen und dann sehr starr zu werden, da die grundlegenden Klassen tief unter dem Code des Programmierers einfach "falsch" wurden. Ohne eine Möglichkeit, die ursprüngliche Klasse einfach zu ändern, könnten ernsthafte Probleme auftreten.

Dynamische Sprachen wie Smalltalk ermöglichten solche Veränderungen über bekannte Methoden in den Klassen; durch die Änderung der Klasse würden die darauf basierenden Objekte ihr Verhalten ändern. Solche Änderungen mussten jedoch sehr sorgfältig vorgenommen werden, da andere Objekte, die auf derselben Klasse basieren, möglicherweise dieses "falsche" Verhalten erwarten: "falsch" ist oft kontextabhängig. (Dies ist eine Form des fragilen Basisklassenproblems .) Außerdem kann in Sprachen wie C++ , in denen Unterklassen getrennt von Oberklassen kompiliert werden können, eine Änderung an einer Oberklasse tatsächlich vorkompilierte Unterklassenmethoden zerstören. (Dies ist eine andere Form des fragilen Basisklassenproblems und auch eine Form des fragilen binären Schnittstellenproblems .)

In Self und anderen prototypbasierten Sprachen wird die Dualität zwischen Klassen und Objektinstanzen beseitigt.

Anstatt eine "Instanz" eines Objekts zu haben, die auf einer "Klasse" basiert, erstellt man in Self eine Kopie eines bestehenden Objekts und ändert es. So Bob's carwürde erstellt, indem eine Kopie eines vorhandenen "Fahrzeug" -Objekts erstellt und dann die Methode " Fahren schnell " hinzugefügt wird, um die Tatsache zu modellieren, dass es sich um einen Porsche 911 handelt . Grundlegende Objekte, die hauptsächlich zum Erstellen von Kopien verwendet werden, werden als Prototypen bezeichnet . Diese Technik soll die Dynamik stark vereinfachen. Wenn sich ein vorhandenes Objekt (oder eine Menge von Objekten) als unzureichendes Modell herausstellt, kann ein Programmierer einfach ein modifiziertes Objekt mit dem richtigen Verhalten erstellen und stattdessen dieses verwenden. Code, der die vorhandenen Objekte verwendet, wird nicht geändert.

Beschreibung

Self-Objekte sind eine Sammlung von "Slots". Slots sind Accessor-Methoden, die Werte zurückgeben. Ein Doppelpunkt nach dem Namen eines Slots legt den Wert fest. Zum Beispiel für einen Slot namens "name",

myPerson name

gibt den Wert in name zurück und

myPerson name:'foo'

stellt es ein.

Self verwendet wie Smalltalk Blöcke für die Flusskontrolle und andere Aufgaben. Methoden sind Objekte, die zusätzlich zu Slots Code enthalten (die sie für Argumente und temporäre Werte verwenden) und können wie jedes andere Objekt in einem Self-Slot platziert werden: eine Zahl zum Beispiel. Die Syntax bleibt in beiden Fällen gleich.

Beachten Sie, dass in Self nicht zwischen Feldern und Methoden unterschieden wird: Alles ist ein Slot. Da der Zugriff auf Slots über Nachrichten den Großteil der Syntax in Self ausmacht, werden viele Nachrichten an "self" gesendet und das "self" kann weggelassen werden (daher der Name).

Grundsyntax

Die Syntax für den Zugriff auf Slots ähnelt der von Smalltalk. Es stehen drei Arten von Nachrichten zur Verfügung:

einstellig
receiver slot_name
binär
receiver + argument
Stichwort
receiver keyword: arg1 With: arg2

Alle Nachrichten geben Ergebnisse zurück, sodass der Empfänger (sofern vorhanden) und die Argumente selbst das Ergebnis anderer Nachrichten sein können. Nach einer Nachricht mit einem Punkt bedeutet, dass Self den zurückgegebenen Wert verwirft. Beispielsweise:

'Hello, World!' print.

Dies ist die Self-Version des Hello-World- Programms. Die 'Syntax gibt ein Literalzeichenfolgenobjekt an. Andere Literale umfassen Zahlen, Blöcke und allgemeine Objekte.

Die Gruppierung kann durch die Verwendung von Klammern erzwungen werden. In Ermangelung einer expliziten Gruppierung wird davon ausgegangen, dass die unären Nachrichten die höchste Priorität haben, gefolgt von der binären (Gruppierung von links nach rechts) und den Schlüsselwörtern mit der niedrigsten. Die Verwendung von Schlüsselwörtern für die Zuweisung würde zu zusätzlichen Klammern führen, wenn Ausdrücke auch Schlüsselwortnachrichten enthalten, um zu vermeiden, dass Self verlangt, dass der erste Teil einer Schlüsselwortnachrichtenauswahl mit einem Kleinbuchstaben beginnt und nachfolgende Teile mit einem Großbuchstaben beginnen.

valid: base bottom
          between: ligature bottom + height
          And: base top / scale factor.

kann eindeutig geparst werden und bedeutet dasselbe wie:

valid: ((base bottom)
            between: ((ligature bottom) + height)
            And: ((base top) / (scale factor))).

In Smalltalk-80 würde der gleiche Ausdruck wie folgt aussehen:

valid := self base bottom
             between: self ligature bottom + self height
             and: self base top / self scale factor.

unter der Annahme base, dass , ligature, heightund scalekeine Instanzvariablen von sind, selfsondern Methoden.

Neue Objekte herstellen

Betrachten Sie ein etwas komplexeres Beispiel:

labelWidget copy label: 'Hello, World!'.

erstellt eine Kopie des "labelWidget"-Objekts mit der Kopiernachricht (diesmal keine Verknüpfung) und sendet ihm dann eine Nachricht, um "Hello, World" in den Slot namens "label" einzufügen. Um nun etwas damit zu machen:

(desktop activeWindow) draw: (labelWidget copy label: 'Hello, World!').

In diesem Fall (desktop activeWindow)wird zuerst das ausgeführt, wobei das aktive Fenster aus der Liste der Fenster zurückgegeben wird, die dem Desktop-Objekt bekannt sind. Als nächstes (von innen nach außen, von links nach rechts lesen) gibt der zuvor untersuchte Code das labelWidget zurück. Schließlich wird das Widget in den Draw-Slot des aktiven Fensters gesendet.

Delegation

Theoretisch ist jedes Self-Objekt eine eigenständige Einheit. Self hat weder Klassen noch Metaklassen. Änderungen an einem bestimmten Objekt wirken sich nicht auf andere aus, aber in einigen Fällen ist es wünschenswert, wenn dies der Fall ist. Normalerweise kann ein Objekt verstehen nur Nachrichten an seine lokalen Schlitzen entsprechen, aber durch eine oder mehrere Schlitze anzeigt Mutter Objekte kann ein Objekt delegieren jede Nachricht , die er nicht selbst Gegenstand der Mutter nicht versteht. Jeder Slot kann zu einem Elternzeiger gemacht werden, indem ein Sternchen als Suffix hinzugefügt wird. Auf diese Weise übernimmt Self Aufgaben, die in klassenbasierten Sprachen Vererbung verwenden würden . Die Delegierung kann auch verwendet werden, um Funktionen wie Namespaces und lexikalische Bereiche zu implementieren .

Angenommen, ein Objekt wird als "Bankkonto" definiert, das in einer einfachen Buchhaltungsanwendung verwendet wird. Normalerweise würde dieses Objekt mit den darin enthaltenen Methoden erstellt, vielleicht "deposit" und "withdraw" und alle von ihnen benötigten Daten-Slots. Dies ist ein Prototyp, der nur in seiner Verwendung eine Besonderheit darstellt, da es sich auch um ein voll funktionsfähiges Bankkonto handelt.

Züge

Wenn Sie einen Klon dieses Objekts für "Bobs Konto" erstellen, wird ein neues Objekt erstellt, das genau wie der Prototyp beginnt. In diesem Fall haben wir die Slots inklusive der Methoden und eventueller Daten kopiert. Eine üblichere Lösung besteht jedoch darin, zuerst ein einfacheres Objekt zu erstellen, das als Traits-Objekt bezeichnet wird und die Elemente enthält, die man normalerweise einer Klasse zuordnen würde.

In diesem Beispiel hätte das Objekt "Bankkonto" nicht die Einzahlungs- und Auszahlungsmethode, aber als übergeordnetes Objekt ein entsprechendes Objekt. Auf diese Weise können viele Kopien des Bankkontoobjekts erstellt werden, aber wir können trotzdem das Verhalten aller ändern, indem wir die Slots in diesem Stammobjekt ändern.

Wie unterscheidet sich das von einer traditionellen Klasse? Betrachten Sie die Bedeutung von:

myObject parent: someOtherObject.

Dieser Auszug ändert die "Klasse" von myObject zur Laufzeit, indem der Wert geändert wird, der dem Slot 'parent*' zugeordnet ist (der Stern ist Teil des Slot-Namens, aber nicht die entsprechenden Nachrichten). Anders als bei Vererbung oder lexikalischem Bereich kann das Delegate-Objekt zur Laufzeit geändert werden.

Slots hinzufügen

Objekte in Self können so geändert werden, dass sie zusätzliche Slots enthalten. Dies kann mit der grafischen Programmierumgebung oder mit dem primitiven '_AddSlots:' erfolgen. Ein Primitiv hat dieselbe Syntax wie eine normale Schlüsselwortnachricht, aber sein Name beginnt mit dem Unterstrich. Das Primitiv _AddSlots sollte vermieden werden, da es ein Überbleibsel früherer Implementierungen ist. Wir werden es jedoch im folgenden Beispiel zeigen, da es den Code kürzer macht.

In einem früheren Beispiel ging es darum, eine einfache Klasse namens Vehicle umzugestalten, um das Verhalten zwischen Pkw und Lkw unterscheiden zu können. In Self würde man dies mit etwas wie diesem erreichen:

_AddSlots: (| vehicle <- (|parent* = traits clonable|) |).

Da der Empfänger des '_AddSlots:'-Primitiven nicht angegeben ist, ist es "self". Bei Ausdrücken, die an der Eingabeaufforderung eingegeben werden, handelt es sich um ein Objekt namens "Lobby". Das Argument für '_AddSlots:' ist das Objekt, dessen Slots auf den Empfänger kopiert werden. In diesem Fall handelt es sich um ein Literalobjekt mit genau einem Slot. Der Name des Slots ist 'vehicle' und sein Wert ist ein weiteres wörtliches Objekt. Die Notation "<-" impliziert einen zweiten Slot namens 'vehicle:', der verwendet werden kann, um den Wert des ersten Slots zu ändern.

Das "=" gibt einen konstanten Slot an, daher gibt es kein entsprechendes "Elternteil:". Das Literalobjekt, das den Anfangswert von 'vehicle' darstellt, enthält einen einzelnen Slot, damit es Nachrichten im Zusammenhang mit dem Klonen verstehen kann. Ein wirklich leeres Objekt, das als (| |) oder einfacher als () angezeigt wird, kann überhaupt keine Nachrichten empfangen.

vehicle _AddSlots: (| name <- 'automobile'|).

Hier ist der Empfänger das vorherige Objekt, das nun zusätzlich zu 'parent*' die Slots 'name' und 'name:' enthält.

_AddSlots: (| sportsCar <- vehicle copy |).
sportsCar _AddSlots: (| driveToWork = (''some code, this is a method'') |).

Obwohl "Fahrzeug" und "Sportwagen" zuvor genau gleich waren, enthält letzteres jetzt einen neuen Slot mit einer Methode, die das Original nicht hat. Methoden können nur in konstante Slots aufgenommen werden.

_AddSlots: (| porsche911 <- sportsCar copy |).
porsche911 name:'Bobs Porsche'.

Das neue Objekt 'porsche911' hat genau wie 'sportsCar' angefangen, aber die letzte Nachricht hat den Wert seines 'name'-Slots geändert. Beachten Sie, dass beide immer noch genau die gleichen Slots haben, obwohl einer von ihnen einen anderen Wert hat.

Umfeld

Ein Merkmal von Self ist, dass es auf derselben Art von virtuellen Maschinensystemen basiert , die frühere Smalltalk-Systeme verwendet haben. Das heißt, Programme sind keine eigenständigen Einheiten wie in Sprachen wie C , sondern benötigen ihre gesamte Speicherumgebung, um ausgeführt zu werden. Dies erfordert, dass Anwendungen in Blöcken des gespeicherten Speichers ausgeliefert werden, die als Schnappschüsse oder Bilder bezeichnet werden . Ein Nachteil dieses Ansatzes besteht darin, dass Bilder manchmal groß und unhandlich sind; Das Debuggen eines Images ist jedoch häufig einfacher als das Debuggen herkömmlicher Programme, da der Laufzeitstatus einfacher zu überprüfen und zu ändern ist. (Der Unterschied zwischen quellenbasierter und bildbasierter Entwicklung ist analog zum Unterschied zwischen klassenbasierter und prototypischer objektorientierter Programmierung.)

Zudem ist die Umgebung auf den schnellen und kontinuierlichen Wechsel der Objekte im System zugeschnitten. Das Refactoring eines "Klassen"-Designs ist so einfach wie das Ziehen von Methoden aus den bestehenden Vorfahren in neue. Einfache Aufgaben wie Testmethoden können erledigt werden, indem Sie eine Kopie erstellen, die Methode in die Kopie ziehen und dann ändern. Im Gegensatz zu herkömmlichen Systemen hat nur das geänderte Objekt den neuen Code und es muss nichts neu erstellt werden, um es zu testen. Wenn die Methode funktioniert, kann sie einfach wieder in den Vorfahren gezogen werden.

Leistung

Self-VMs erreichten bei einigen Benchmarks eine Leistung von etwa der Hälfte der Geschwindigkeit von optimiertem C.

Dies wurde durch Just-in-Time-Kompilierungstechniken erreicht , die in der Selbstforschung bahnbrechend und verbessert wurden, um eine Hochsprache so gut zu machen.

Müllabfuhr

Der Garbage Collector für Self verwendet eine generationsübergreifende Garbage Collection, die Objekte nach Alter trennt. Durch Verwendung des Speicherverwaltungssystems zum Aufzeichnen von Seitenschreibvorgängen kann eine Schreibsperre aufrechterhalten werden. Diese Technik bietet eine hervorragende Leistung, obwohl nach einiger Zeit eine vollständige Garbage Collection erfolgen kann, die einige Zeit in Anspruch nimmt.

Optimierungen

Das Laufzeitsystem glättet selektiv Anrufstrukturen. Dies führt an sich zu bescheidenen Beschleunigungen, ermöglicht jedoch ein umfangreiches Caching von Typinformationen und mehrere Codeversionen für verschiedene Aufrufertypen. Dadurch entfällt die Notwendigkeit, viele Methodensuchvorgänge durchzuführen, und ermöglicht das Einfügen von bedingten Verzweigungsanweisungen und hartcodierten Aufrufen – was oft eine C-ähnliche Leistung ohne Verlust an Allgemeingültigkeit auf Sprachebene ergibt, jedoch auf einem vollständig müllgesammelten System.

Siehe auch

Verweise

Weiterlesen

Externe Links