Detailfrage bei eigener Sprachdefinition - wie würdet Ihr es machen?
-
Ich versuche zur Zeit eine eigene Sprache zu entwerfen, die grundsätzlich auf der C++/JAVA Syntax basiert, aber einige Eigenheiten hat. Eine dieser Eigenheiten soll sein, dass bereits bestehende Klassendefinitionen abgeändert werden können, Beispiel:
class Bar; class Foo { Int i; Bar b; Foo(Int ni) : i(ni) {}; public: void DoSomething() {/*...*/}; void DoAnotherTjing() {/*...*/}; //... mehr Methoden etc. };
jetzt einige Änderungen:
@morph class Foo //class Foo wird geändert { @remove b; //Bar b wird entfernt... const Bar b; //...und const wieder eingeführt public: @asis Foo(Int); //Konstruktor wird in den public breich verschoben @morph void DoSomething() //DoSomething wird geändert: { /* neue Implementation */ }; @morph void DoAnotherTjing() //DoAnotherTjing wird geändert: void DoAnotherThing() //Neue Deklaration { @asis //rest bleibt gleich }; Int get() //neue Methode { return i; } //Rest bleibt wie gehabt };
Ich habe jetzt das Ändern mit @morph, @asis, @remove so hingeschrieben wie es mir momentan vorschwebt, hättet ihr andere Vorschläge, wie man das evtl. eleganter machen könnte?
Ein paar Zusatzinfos:
- wie in Java soll es keine freien Funktionen gebenFolgende Änderungen sollen möglich sein:
- Hinzufügen und Entfernen von Membervariablen (@remove fürs Entfernen)
- Hinzufügen und Entfernen von Methoden (@remove fürs Entfernen)
- Ändern von Zugriffsspezifizierern für Methoden und Member (durch redeklaration oder wie oben mit @asis)
- Ändern von Funktionsdeklarationen (returntype, argumentenliste, defaultargumente, const)
- Ändern von Funktionsdefinitionen
-
Kleine Gegenfrage, worin ist die Motivation darin und wann soll dieses Konzept greifen?
-
Sofern es für Dich nicht nur experimentellen Charakter hat, stellt sich mir die Frage, worin der Sinn hierin liegt. Du kannst so an verschiedenen Stellen im Programmfluss ein gegebenes Interface ändern. Das viel zu gefährlich, als das es nützlich sein kann.
-
Tachyon schrieb:
Das viel zu gefährlich, als das es nützlich sein kann.
das ist doch bloss 'ne erweiterte form von überladung. bereits lebende objekte sollen bestimmt nicht davon beeinflusst werden. oder doch?
-
Geplant ist, damit folgendes zu realisieren:
Ein Server verwaltet die Klassen sowie die von den Klassen inatantiierten Objekte. Ferner bietet er eine Reihe Funktionen an, die von Clients aufgerufen werden können. Dieses System soll laufend fortentwickelt werden können, ohne den Server neu starten zu müssen, daher die Möglichkeit, vorhandene Klassendefinitionen zu ändern. Selbstverständlich stellt das hohe Anforderungen an die Code-Analyse, gerade beim Entfernen von Methoden muss sichergestellt sein dass alle Aufrufe dieser Methoden ebenfalls entfernt worden sind usw.Motiviert ist das Ganze aus der Entwicklung von Browsergames. Häufig werden solche Spiele bereits in sehr frühen Entwicklungsstadien verfügbar gemacht, die tester sind ganz normale User die im Grunde einfach spielen wollen. Daraus ergibt sich die Anforderung, dass das System zwischendurch weder neugestartet noch zeitweilig vom Netz genommen wird sondern dass die Entwicklung sozusagen on the fly am laufenden System erfolgt. Die Idee für dieses System hat sich im Laufe der Jahre verselbständigt und entwickelt.
Allerdings ist der Sinn oder Unsinn eines solchen Systems bzw. einer solchen Sprache hier nicht unbedingt von Belang, es ging mir ja nur darum, wie man solche Änderungen möglicherweise zum Ausdruck bringen kann
-
pumuckl schrieb:
...es ging mir ja nur darum, wie man solche Änderungen möglicherweise zum Ausdruck bringen kann
ich finde das mit den @-keywords ganz gut. was besseres fällt mir jedenfalls nicht ein.
-
Axo, also versteh ich so, dass du die Ändern in eine Andere Datei schreibt, und diese vom Compiler/Interpreter durchgeführt werden anhand deine @-Direktiven?
-
Wenn es dir um das Ziel geht (und nicht den Weg, eine Sprache selber zu entwickeln), würde ich eine Skriptsprache nehmen und die innerhalb von Java (Java-Scripting Unterstützung seit 1.6) laufen lassen. Ruby (bzw. JRuby als Scripting-Engine) unterstützt die Features, die du haben willst.
Warum das Rad neu erfinden?
-
-fricky- schrieb:
Tachyon schrieb:
Das viel zu gefährlich, als das es nützlich sein kann.
das ist doch bloss 'ne erweiterte form von überladung. bereits lebende objekte sollen bestimmt nicht davon beeinflusst werden. oder doch?
Jein. Da es sich im Prinzip um Objekte in einer Datenbank handeln soll, über die parallel verschiedene Prozesse drüberlaufen sollen, ist eine Änderung der Objekte grundsätzlich schon möglich. Ich stelle mir das momentan so vor:
Das System befindet sich im Zustand/Version X, der Admin/Entwickler spielt eine Codeänderung ein, um es in Zustand/Version Y zu überführen. Da zur gleichen Zeit natürlich noch Clientanfragen aus dem Zustand X abgearbeitet werden, kann die Codeänderung nicht sofort aufs System übertragen werden. Ein mögliches Vorgehen wäre also:
- Das Interface für Clientaufrufe wird auf Version Y umgestellt, die neuen Aufrufe werden erstmal in eine Warteschleife gesetzt
- alle Verion X-Clientanfragen werden abgearbeitet
- danach werden alle Objekte nach Version Y überführt
- die Version Y Clientaufrufe werden laufengelassen.Eine andere Version wäre, mit dem Interface alle nicht geänderten Objekte bereits mit version Y zu flaggen, Objektänderungen werden erst durchgeführt wenn alle version X-Anfragen abgearbeitet sind. Version Y-Anfragen blockieren, sobald sie auf ein altes version X-Objekt treffen und werden erst fortgeführt wenn dieses Objekt geändert und mit Version Y geflaggt wurde.
tfa schrieb:
Wenn es dir um das Ziel geht (und nicht den Weg, eine Sprache selber zu entwickeln), würde ich eine Skriptsprache nehmen und die innerhalb von Java (Java-Scripting Unterstützung seit 1.6) laufen lassen. Ruby (bzw. JRuby als Scripting-Engine) unterstützt die Features, die du haben willst.
Warum das Rad neu erfinden?Mir gehts um beides. Auch darum, am Ende die Möglichkeit zu haben, mit einer Sprache so ein System zu bearbeiten, ohne irgendwelche Hintertürchen, zusätzliche Scriptsprachen oder freakige Hacks, und ohne eine Sprache zu verbiegen die für diese Art Anwendung nicht gedacht und nur mäßig geeignet ist.
-
Warum willst du das als Sprachbestandteil einführen? Schaut sehr komisch aus. Ist dann der ganze Quellcode nach 10 Jahren Entwicklung voll von Änderungsanweisungen für Methoden, die irgend wann mal verwendet wurden?
Warum nicht einfach die neue Definition kompilieren und dann in die Laufzeitumgebung laden. Schau dir zB mal an, wie das zB in Lisp funktioniert (da kannst du Klassen umschreiben und neukompilieren, während das Programm läuft).
Willst du einen komplett neuen Compiler entwerfen oder soll das nur ein Zusatz für einen bestehenden Compiler sein?
-
Hm, das Argument mit dem jahrhundertealten Änderungen ist natürlich richtig. Auf der anderen Seite stellt sich mir das Problem, dass ich bei einem kompletten Neukompilieren den Bezug zur alten Version verliere und vor allem die Übergangsfunktion (wie mache ich aus einem Objekt Version X eins von Version Y) schwierig umzusetzen wird. Im Moment schweben mir zwei Möglichkeiten vor:
-
Ich gebe jeweils nur die Änderungen ein. Der Server speichert unter anderem eine Zwischendarstellung des Codes (z.B. in XML) ab, baut die Änderungen dort ein und bei Bedarf kann ich rückgenerierten Originalcode anfordern um eine vollständige Klassendefinition vor mir zu haben.
-
da ich lieber immer die volle Klassendefinition vorliegen habe, checke ich einfach die komplette geänderte Definition ein, und auf dem Server wird der Code (oder Zwischencode) mit dem alten verglichen und so die eigentlichen Änderungen rausgefunden. Gleichgebliebene Member brauchen dann beim Versionswechsel nicht explizit reinitialisiert zu werden.
Nr 2) könnte man natürlich mit einem Versionskontrollsystem verbinden, da viele davon intern sich mit diff & Co die Änderungen der verwalteten Code-Dateien merken statt jedesmal die komplette Datei zu speichern.
Eine Mischung aus beiden Systemen wäre natürlich auch denkbar - dann bräuchte ich aber eben wieder die morph/remove kommandos.
-
-
und vor allem die Übergangsfunktion (wie mache ich aus einem Objekt Version X eins von Version Y) schwierig umzusetzen wird.
Ne, das ist doch eigentlich nicht so schwer. Jedes Objekt enthält eben einen Zeiger auf die Klasse. Die Klasse stellt dabei eben einen Dispatch für die Methoden (eben wie ein vtable, nur dass die Methoden eben über ihre (mangeled) Namen und keine fixen Pointer angesprochen werden) zur Verfügung. Beim Übergang musst du dann nur die Dispatch-Tabelle anpassen.
Aber schau dir vielleicht ein paar dynamischere Sprachen, als C++ oder Java an. Die haben alle etwas in die Richtung (Lisp, Smalltalk, Erlang, Objective C und die ganzen Skriptsprachen wie zB Python).
(Bitte nicht böse sein, aber in dem Zusammenhang, sieht das nicht nur so aus, als wolltest du das Rad neu erfinden, sondern als würdest du das Rad in Form eines Quaders neu erfinden. :)).
-
Hier mal ein Beispiel, wie sowas in Ruby ansehen könnte:
class A public def eine_methode puts "Hallo" end private def geheim puts "geheim" end public def veraenderlich puts "Hü!" end end class B < A ## Methode entfernen undef_method :eine_methode ## private Methode public machen public :geheim ## Alias für Methode anlegen alias_method :alt, :veraenderlich ## Methode umdefinieren def veraenderlich puts "Hott!" end end a=A.new a.eine_methode -> "Hallo" a.geheim -> Fehler a.veraenderlich -> "Hü!" b=B.new b.eine_methode -> Fehler b.geheim -> "geheim" b.veraenderlich -> "Hott!" b.alt -> "Hü!"
Und das alles funktioniert schon - es ist rund und dreht sich...
-
sieht garnicht mal so schlecht aus... wie stehts da mit Typsicherheit, und kann ich auch eine ganze Klasse umdefinieren?
-
Ruby und viele andere Skriptsprachen sind dynamisch typisiert. Eine Typsicherheit wie in den klassischen, statischen Sprachen gibt es da nicht. Stichwort: Duck-Typing.
Was meinst du mit "ganze Klasse umdefinieren"?