Die zweite Sprache, die ich auf Eignung für interne DSLs testen will, ist Groovy. Groovy ist eine dynamische Sprache. Sie läuft ebenfalls in der Java VM. Die Syntax ist sehr ähnlich zu Java und damit wesentlich umfangreicher als die Syntax von Clojure. Dieses mehr an Syntax bedeutet gleichzeitig, dass mehr Techniken nötig sind um die Hostsprache Groovy so zu verbiegen, dass sie das "Look&Feel" einer DSL hat. In diesem ersten Teil werde ich einige Techniken von Groovy vorstellen, die zur Erstellung von internen DSLs genutzt werden können. Im zweiten Teil werde ich dann die Fragebogen DSL aus dem Clojure Beitrag mit Groovy umsetzen.
Techniken für die Erstellung von DSLs
Einsparung von Semikolons und Klammern
In Groovy sind Semikolons in den meisten Fällen optional. Klammern bei Funktionsaufrufen können auf der obersten Ebene ebenfalls entfallen.
//In Java und Groovy gültig println("Test"); println("Hello"); println("World"); println(Integer.parseInt("3")); println "Test" //funktioniert println "Hello" println "World" //funktioniert nicht println "Hello"; println "World" //funktioniert println Integer.parseInt "3" //funktioniert nicht println Integer.parseInt("3") //funktioniert
Neue Methoden und Eigenschaften für Klassen zur Laufzeit hinzufügen
In Groovy ist es möglich jeder existierenden Klasse neue Methoden und Eigenschaften zur Laufzeit hinzuzufügen, ohne die Klassen neu kompilieren zu müssen. Dazu wird der ExpandoMetaClass-Mechanismus verwendet. Damit besteht z.B. die Möglichkeit Einheiten an Zahlen anhängen zu können (z.B. "1.km" oder "1.s") und entsprechende Umwandlungen durchzuführen, wie im folgenden Beispiel zu sehen ist.
//Vererbung von neuen Methoden und Eigenschaften welche über //den ExpandoMetaClass-Mechanismus hinzugefügt werden ist //standardmäßig deaktiviert. Mit der folgenden Zeile wird //Vererbung aktiviert. ExpandoMetaClass.enableGlobally() Number.metaClass.getProperty = {String einheit -> switch(einheit) { //delegate ist die Instanz für welche die Eigenschaft //abgerufen wird. case 'km': return delegate * 1000 case 'm': return delegate case 'cm': return delegate * 0.01 case 'mm': return delegate * 0.001 default: 'Die Einheit ' + einheit + ' ist unbekannt.' } } println(1.km) println(10.cm) println(20.ha)
Bei Ausführung des Skriptes entsteht folgende Ausgabe:
1000 0.10 Die Einheit ha ist unbekannt.
Mehr zu dem Thema kann man hier nachlesen.
Abfangen nicht definierter Variablen
In jedem Groovy-Skript existiert eine Instanz der Klasse "groovy.lang.Binding". Diese Instanz löst alle nicht im Skript definierten Variablen auf. Dieser Mechanismus wird u.a. genutzt um Informationen zwischen dem Groovy Skript und der aufrufenden Sprache auszutauschen. Die aktuelle Instanz ist über die Variable "binding" erreichbar. Ersetzt man diese aktuelle Instanz mit einer Instanz einer eigenen Klasse welche von "groovy.lang.Binding" erbt, kann man das Verhalten des Skriptes bei nicht definierten Variablen selber festlegen. Das folgende Beispiel demonstriert diese Möglichkeit.
class TestBinding extends Binding { def getVariable(String name) { println("Variable nicht gefunden") return null } } binding = new TestBinding() nichtDefinierteVariable
Die Ausgabe bei Ausführung des Skriptes ist "Variable nicht gefunden", der Rückgabewert ist null.
Überladen von Operatoren
Ähnlich wie in C++ können Operatoren in Groovy überladen werden. Jeder Operator wird durch eine Methode repräsentiert, welche man in der eigenen Klasse einfach implementieren muss, um den entsprechenden Operator zu überschreiben. Eine Liste mit den Methodennamen findet man hier.
class Kaffe { List zutaten = [] def leftShift(String zutat) { this.zutaten << zutat } def inhalt() { println("Im Kaffe ist: ") zutaten.each {it -> print("* "); println(it)} } } kaffe = new Kaffe() kaffe << "Wasser" kaffe << "gemahlene Bohnen" kaffe << "Milch" kaffe.inhalt();
Ergibt die Ausgabe
Im Kaffe ist: * Wasser * gemahlene Bohnen * Milch
Methodenzeiger
Es ist möglich Zeiger auf Methoden einer bestimmten Instanz einer Klasse zu erstellen.
class Pflanze { def int wassermenge def gießen(int menge) { wassermenge += menge } } kaktus = new Pflanze(); def gieße = kaktus.&gießen gieße 300 gieße 100
Keine Kommentare:
Kommentar veröffentlichen