In der stürmischen See dynamischer Programmierung

Eclipse Golo – leichtgewichtig und dynamisch auf der JVM [Pirates of the JVM]

Julien Ponge

Heute setzen wir im Zuge unserer Serie Pirates oft he JVM Segel in Richtung der stürmischen See dynamischer Programmiersprachen. Der erste sichere Hafen ist die Insel Golo, gewidmet der gleichnamigen Sprache aus dem Eclipse-Universum. In dieser Einführung zeigt Golo-Erfinder Julien Ponge, wie man mit Golo durchstarten kann. Capitaine Ponge, übernehmen Sie das Steuer!

Was, noch eine Programmiersprache? Das ist wirklich eine gute Frage, denn die Java Virtual Machine (JVM) ist ja bereits Host für eine Vielzahl von Sprachen mit erprobter Anwendbarkeit. Obwohl Golo tatsächlich „noch ein Programmiersprache“ ist, bietet sie für Bastler interessante Anwendungsmöglichkeiten sowohl als eigenständige Sprache, als eingebettete Programmiersprache für andere JVM-Anwendungen oder sogar für schnelles Prototyping in IoT-Settings.

Klick auf die Karte für die komplette Infografik.

Eclipse Golo entsprang aus Experimenten vom Dynamid Forschungsteam am CITI-Inria Laboratory und der INSA-Lyon. Als wir die Möglichkeiten untersucht haben, die von der neuen JVM-Bytecode-Anweisung invokeDynamic in Java 7 angeboten wurden, haben wir um die Anweisung und das korrespondierende API java.lang.invoke herum schließlich eine simple Programmiersprache designt. Aus folgenden Gründen dachten wir, es wäre interessant die Entwicklung von Golo weiterzuverfolgen:

  1. Während einige andere JVM-Sprachen ihre Semantiken basierend auf invokeDynamic portieren, sind wir den anderen Weg gegangen und haben die Sprache, die Leistungsmuster im Hinterkopf behaltend, drumherum designt.
  2. Andere erprobte JVM-Sprachen tendieren dazu, einschüchternde Codebasen zu haben, vor allem für Studenten. Wir brauchten einfach etwas Zugänglicheres, um mit der Sprache und der Runtime zu experimentieren.

Golo ist jetzt ein Technologieprojekt der Eclipse Foundation und eine kleine, freundliche Community kümmert sich um deren Weiterentwicklung.

Starten mit Golo

Golo-Dateien nutzen für gewöhnlich das Suffix .golo; jede Golo-Datei definiert ein Modul. Siehe etwa hier:

 
module EchoArgs
 
function main = |args| {
  println("Here are the command line arguments, if any:")
  foreach arg in args {
    println("-> " + arg)
  }
}

Die Main-Funktion eines Moduls kann als Einstiegspunkt für das jeweilige Programm dienen, Kommandozeilenparameter werden als Array übergeben. Golo-Programme können zu JVM-Bytecode kompiliert oder direkt ausgeführt werden. Für beide Fälle bietet das Kommandozeilen-Tool von Golo einen spezifischen Unterbefehl an:

 
$ golo compile echo-args.golo
$ golo golo --files echo-args.golo --args 1 2 3 4 5
Here are the command line arguments, if any:
-> 1
-> 2
-> 3
-> 4
-> 5
$

Der Unterbefehl compile kompiliert den Golo-Code zu JVM Bytecode, run führt bereits kompilierten Golo-Code aus und golo dient als Abkürzung, um Code zu kompilieren und direkt auszuführen. Außerdem werden „Shebang“-Skripte im Unix-Stil unterstützt:

 
module EchoArgs
 
function main = |args| {
  println("Here are the command line arguments, if any:")
  foreach arg in args {
    println("-> " + arg)
  }

Indem die .golo-Datei ausführbar gemacht wird, können wir sie direkt ausführen:

 
$ chmod +x echo-args.golo
$ ./echo-args.golo 1 2 3 4 5
Here are the command line arguments, if any:
-> 1
-> 2
-> 3
-> 4
-> 5
$

Collection Literals, anonyme Funktionen und Collection Comprehensions

Golo bietet Support für Collection Literals: Tuples, Lists, Vectors, Sets und Maps. Wie folgt lässt sich eine Liste erzeugen:

 
# Note: an instance of java.util.ArrayList
#
# This is equivalent to:
#   let numbers = java.util.ArrayList()
#   numbers: add(1)
#   (...)
let numbers = list[1, 2, 3, 4, 5]

Alle Collection Literals werden von n-ary Tuples der java.utilCollections gestützt, die auf einer für Golo spezifischen Implementierung beruhen. Dies sorgt für eine großartige Interoperabilität mit Java-Bibliotheken.

In Golo ist es durch die Verwendung eines : zwischen Method und Empfängerobjekt möglich, Methods für Objekte aufzurufen. Collections haben zusätzliche Methods, um funktionale Idiome zu unterstützen. Der nachfolgende Code nimmt die Liste numbers von oben, generiert eine neue Liste, die unter Verwendung von map die Inhalte um die Zahl 10 erhöht, und berechnet dann die Summe aller Elemente mit reduce:

 
let sum = numbers:
  map(|n| -> n + 10):
  reduce(0, |acc, next| -> acc + next)

Wir können beobachten, wie anonyme Funktionen an die map– und reduceMethods übergeben werden. Es ist möglich, einer Funktion eine Referenz zuzuweisen, sie mit einer anderen Funktion zu kombinieren und namentlich aufzurufen.

 
let f = (|n| -> n + 1): andThen(|n| -> n * 2)
println(f(3))   # prints 8

Zu guter Letzt stellt Golo Collection Comprehensions zur Verfügung, die denen von Python ähnlich sind:

nums ist hier ein Set von Tuples [i,j], bei dem i eine grade Nummer zwischen 1 und 100 ist, j ein Buchstabe zwischen a und z. So werden außerdem die Range-Notationen eingeführt, wie bei [1..100].

Typen definieren

Golo unterstützt zwar nicht die Definition von Klassen, aber es unterstützt Structure-, Union- und Dynamic-Object-Typen.

Dynamische Objekte

Die einfachste Definition eines Typs ist DynamicObject:

 
let p1 = DynamicObject():
  define("name", "Someone"):
  define("email", "someone@some-provider.tld"):
  define("prettyPrint", |this| -> println(this: name() + " <" + this: email() + ">"))
 
p1: prettyPrint()

Die reservierte Method define ermöglicht es, Attribute und Methoden zu erstellen, die dann per Name genutzt werden können. In vielerlei Hinsicht ähnelt diese Konstruktion den Expando-Objekten in Groovy. Es existiert auch eine Fallback-Method-Definition, die alle Aufrufe zu einer Method erfasst, die noch nicht definiert wurde. Das ist sinnvoll, um flexible APIs zu designen, genau wie einige Idiome der Ruby- bzw. Rails-Communities.

Strukturen

Strukturen können wie folgt erstellt werden:

 
struct Point = {x, y}

Hierdurch wird eine Datenstruktur für Points definiert, die kann dann folgendermaßen erstellt und verändert werden kann:

 
var p2 = Point(1, 2)
println(p2)   # "struct Point{x=1, y=2}"
 
p2: x(4)
println(p2: x())
 
let x, y = p2
println(x)
println(y)

Es sollte beachtet werden, dass Golo auch Destructuring unterstützt, sodass x und y die Werte vom Structure-Objekt bekommen. Das funktioniert auch bei Collections, inklusive map-Einträgen.

Diese Point-Definition macht für sich genommen noch nicht viel, aber wir können Methods hinzufügen, indem wir Augmentations verwenden. Augmentations können Methods zu jedem Typ hinzufügen, inklusive existierender Java-Klassen. Eine Augmentation lässt sich auf den Code vom gleichen Modul oder auf den von einem anderen Modul anwenden, das seine Definition importiert. So können wir shift– und dump-Methoden zu Point-Objekten hinzufügen:

 
augment Point {
 
  function shift = |this, dx, dy| ->
    Point(this: x() + dx, this: y() + dy)
 
  function dump = |this| ->
    println("(" + this: x() + ", " + this: y() + ")")
}

Ist die Augmentiation erstmal sichtbar, können wir die Methoden verwenden:

 
p2 = p2: shift(1, 4)
p2: dump()

Aufzählungen

Aufzählungen sind eine elegante Möglichkeit, um individuelle Typen als Tagged Unions zu definieren, die auch als Datentypen algebraischer Summen bekannt sind. Das folgende Listing zeigt, wie man Binärbäume unter Verwendung von Blatt-Typen, leeren Typen und Node-Typen definieren könnte:

 
union Tree = {
  Empty
  Leaf = { value }
  Node = { left, right }
}

Mit dieser Definition können wir sowohl einen Baum, als auch einen leeren Baum erstellen:

 
let t0 = Tree.Empty()
let t1 = Tree.Node(
  Tree.Leaf(1), Tree.Node(
    Tree.Leaf(2), Tree.Leaf(3) ))

Union-Typen bekommen Query Methods, um ihre Typen zu checken, sodass…

 
println(t0: isEmpty())
println(t1: isNode())

für jeden Test true ausgegeben wird, da t0 ein leerer Baum und t1 ein Blatt ist. Zudem ist es möglich, nicht nur auf Typen sondern auch auf Werte zu prüfen:

 
let _ = Unknown.get()
println(t1: isNode(Tree.Leaf(1), _))

Unknown.get() gibt einen Platzhalterwert für Union-Typen zurück, der uns ermöglicht zu überprüfen, ob< code>t1 ein Node ist, dessen left-Wert ein Blatt mit dem Wert 1 ist, während gleichzeitig der right-Wert ignoriert wird.

Dekorierer

Golo unterstützt auch die Python-artige Decorator-Funktion. Ein Decorator (oder Dekorierer) ist eigentlich eine Funktion höherer Ordnung, also eine Funktion, die eine Funktion zurückgibt. Als praktisches Beispiel definieren wir eine trace-Funktion:

 
function trace = |message| -> |fun| -> |args...| {
  println("Calling " + message)
  println(">>> arguments: " + args: toString())
  let res = fun: invoke(args)
  println(">>> return value: " + res)
  return res
}

Die Funktion nimmt eine Nachricht und gibt eine Funktion zurück, die eine andere Funktion dekoriert und die Parameter erfasst (args... ist für Parameter variabler Länge, sodass ist sie eine Funktion beliebiger Arilität erfassen kann). Anschließend können wir eine Funktion wie folgt dekorieren:

 
@trace("Sum function")
function add = |a, b| -> a + b

Wenn wir add(1, 2) aufrufen, kriegen gibt die Konsole Folgendes aus:

 
Calling Sum function
>>> arguments: [1, 2]
>>> return value: 3

Das war erst ein kleiner Überblick!

Dieser Artikel zeigt noch nicht alle Features von Eclipse Golo und wir möchten alle Leser dazu ermutigen, es einfach mal auszuprobieren und sich mit der Community darüber zu unterhalten.

Zudem möchten wir einen besonderen Aufruf an die anderen Eclipse-Projekte starten: Ironischerweise hat Golo bisher keine vernünftige Unterstützung in der Eclipse IDE. Der beste Support sind nach wie vor die Atom- und Vim-Editoren. Wir wären sehr dankbar, wenn sich jemand finden würde, der uns helfen kann, die Eclipse IDE um eine First-Class-Unterstützung für Eclipse Golo zu erweitern.

Dieser Artikel erschien im Original 2016 in der Oktober-Ausgabe des Eclipse Newsletters.

 

Die Pirates of the JVM

Gehen wir auf Schatzsuche! In der Infografik „Pirates of the JVM“ verbirgt sich so manches Programmiersprachen-Juwel, das inspirierte Entwickler auf der Java-Plattform für sich entdecken werden. Doch sehen Sie selbst…

Haben Sie Lust auf Schatzsuche zu gehen? In unserem Gewinnspiel „Pirates of the JVM“ ist Ihr Rätseltalent gefragt. Belohnt werden Sie mit 3 tollen Preisen…

PIRATES OF THE JVM – DAS GEWINNSPIEL

Piratenflagge via Shutterstock.com / katyam1983

Geschrieben von
Julien Ponge
Julien Ponge
Julien Ponge ist IT-Wissenschaftler beim CITI Laboratory.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.