zurück zum Artikel

Ein zweiter Blick auf JVM-Programmiersprachen

Simon Olofsson

Groovy, Scala, Clojure, Ceylon, Kotlin und Fantom sollen frischen Wind auf die Java Virtual Machine bringen. Doch welche Programmiersprache ist für welchen Anwendungsfall sinnvoll, und was macht sie jeweils aus?

Ein zweiter Blick auf JVM-Programmiersprachen

Groovy, Scala, Clojure, Ceylon, Kotlin und Fantom sollen frischen Wind auf die Java Virtual Machine bringen. Doch welche Programmiersprache ist für welchen Anwendungsfall sinnvoll, und was macht sie jeweils aus?

Die Java Virtual Machine (JVM) hat sich im letzten Jahrzehnt von einer Laufzeitumgebung für Java-Programme zu einer polyglotten VM gewandelt. Neben Java gibt es auf ihr inzwischen etliche andere Programmiersprachen. Es existieren sowohl Portierungen bekannter Sprachen, wie Python oder Ruby, als auch Neuentwicklungen. Benjamin Schmid hat in seinem Artikel einen guten Überblick über einige Sprachen gegeben [1], der an dieser Stelle erweitert und vertieft werden soll.

Der vorliegende Artikel beschäftigt sich mit sechs Programmiersprachen, die für die JVM entwickelt wurden und zu Bytecode kompilieren. Xtend und ähnliche Projekte, in denen ein Compiler die Quellen in Java-Code überführt (auch als Transpilierung bezeichnet), sollen nicht berücksichtigt werden.

Einige Beispiele stellen Besonderheiten und interessante Konzepte der unterschiedlichen Programmiersprachen vor, wobei nur wenige davon exklusiv in einer Sprache vorhanden sind. Sie beeinflussen sich vielmehr gegenseitig. Daher ist es umso wichtiger, den Überblick über den aktuellen Stand zu behalten.

Groovy ist eine dynamisch typisierte Programmiersprache, deren Entwicklung 2003 begann. Eine statische Typprüfung ist in ihr optional möglich. Der Einstieg gestaltet sich recht einfach, da die meisten Java-Programme auch in Groovy gültig sind. Im folgenden Code-Ausschnitt definiert der Entwickler mit def eine Funktion und interpoliert den ausgegebenen String beziehungsweise ersetzt die enthaltene
Variable. Es ist zudem ersichtlich, dass Semikolons und Klammern beim Methodenaufruf optional sind.

def sayHello(name) {
println "Hallo $name!"
}

sayHello "JavaLand"

Eine der Stärken von Groovy ist die breite Unterstützung von Collections. So gibt es einen Typ Range, der ein Intervall erzeugt. Im unten stehenden Beispiel ist der Typ explizit angegeben, was sich insbesondere bei Methodenparametern zur besseren Dokumentation empfiehlt. Die Methode collect (der Name stammt aus Smalltalk und ist in anderen Programmiersprachen auch als map bekannt) ruft eine Funktion für jedes Element einer Collection auf und erzeugt aus den Rückgabewerten eine neue Collection. Im Code-Ausschnitt wurde das durch den impliziten Parameter it referenzierte Element mit zwei multipliziert.

Range range = 1..10

def doubled = range.collect {
it * 2
}

Wenn alle Elemente einer Collection auszuwählen sind, ist der Star-Dot-Operator nützlich:

def list = ["Star", "Dot", "Operator"]

def loweredList = list*.toLowerCase()

Das Programm ruft für alle Elemente der Liste die Funktion toLowerCase auf. Auch für Maps gibt es in Groovy vielfältige Verwendungsmöglichkeiten, es lassen sich beispielsweise Funktionen in ihnen speichern und anschließend aufrufen.

def map = [
function: {
println "Eine Funktion in einer Map!"
}
]

map.function()

Ihr Einsatz ist sinnvoll, wenn das Erstellen einer Klasse zu viel Overhead birgt, beispielsweise wenn ein Objekt für Tests zu mocken ist.

Der ungewollte Zugriff auf Nullwerte und die daraus resultierende Null-Pointer-Exception gehören zu den am häufigsten auftretenden Fehlerquellen in Java. Groovy bietet mit dem Elvis-Operator (der übrigens so heißt, weil er an die Haartolle von Elvis Presley erinnert) eine vereinfachte Form des ternären Operators an, um beispielsweise einen Standardwert zu verwenden, wenn eine Variable null ist (null ist in Groovy false). Der im Folgenden gezeigte Operator ist also äquivalent zum Elvis-Operator. Darüber hinaus erspart der Safe-Navigation-Operator die Überprüfung auf Nullwerte bei der Navigation eines Objektgraphen, da er Null zurück gibt, sobald ein Nullwert vorkommt.

def conference = [name: "JavaLand"]

// ternärer Operator
println conference.name ?
conference.name : "Unknown"

// Elvis-Operator
println conference.name ?: "Unknown"

// Safe-Navigation-Operator
conference.name?.toLowerCase()

Als abschließendes Beispiel sollen noch kurz die Builder vorgestellt werden, die das Erstellen von verschachteltem Markup erleichtern. Der folgende Quelltextauszug zeigt, wie sich eine einfache HTML-Seite mit dem XML-Markup-Builder generieren lässt.

def builder =
new groovy.xml.MarkupBuilder()

builder.html {
body {
a(href: 'http://groovy.codehaus.org',
"Mit Groovy erstellt")
}
}

Groovy ist eine ausgereifte Sprache, die einige Verbesserungen gegenüber Java mitbringt. Viele Projekte setzen sie als Ergänzung ein, um bestimmte Teile einer Applikation einfacher entwickeln zu können. So sind Tests, Skripte oder domänenspezifische Sprachen ein oft genannter Anwendungsfall für Groovy. Aber auch als Hauptsprache ist Groovy aufgrund der zahlreichen Features und der optionalen statischen Typprüfung eine gute Wahl. Zur Entwicklung von Webapplikationen bietet sich mit Grails ein robustes und durchdachtes Framework an.

Scala ist – wie Groovy – im Jahr 2003 entstanden. Ein Team um Martin Odersky wollte eine Programmiersprache schaffen, die statisch typisiert ist und objektorientierte mit funktionalen Paradigmen vereint. Im folgenden Codebeispiel ist die statische Typisierung bei dem Methodenparameter ersichtlich, allerdings kommt hier die Postfix-Notation zum Einsatz, das heißt, der Typ steht nach dem Variablenbezeichner.

def sayHello(name: String) {
println(s"Hallo $name!")
}

sayHello("JavaLand")

In Scala gibt es die Konvention, dass Klammern bei Funktionsaufrufen mit Seiteneffekten stets mit angegeben werden, damit deutlich ist, dass es sich nicht um einen Operator handelt. Der Nebeneffekt von sayHello ist die Ausgabe auf der Konsole.

Obwohl Scala statisch typisiert ist, lässt sich die Typangabe in vielen Fällen weglassen. Der Compiler bestimmt dann den passenden Typ (Typinferenz). Im nächsten Listing weiß er etwa, dass setB vom Typ Set[Int] ist und die Typangabe kann entfallen. Bei doubleB ist klar, dass ein Int zurückgegeben wird. Bei Methodenparametern wird der Typ nicht inferiert und ist immer explizit anzugeben.

val setA: Set[Int] = Set(1, 2, 3)
val setB = Set(1, 2, 3)

def doubleA(i: Int): Int = i * 2
def doubleB(i: Int) = i * 2

Ein mächtiges Sprachkonstrukt in Scala sind die For-Ausdrücke. Wie im nächsten Code-Beispiel gezeigt, lassen sie sich zum Iterieren (von 1 bis 10) und Filtern (alle graden Zahlen) verwenden. Wurde ein passendes Element gefunden, kann das Programm es mit yield zurückgeben, sodass sich am Ende eine neue Collection mit allen passenden Elementen erzeugen lässt.

val even = for {
i <- 1 to 10
if i % 2 == 0
} yield i

assert(List(2, 4, 6, 8, 10) == even)

Pattern Matching ist die generalisierte Form des bekannten switch-Statements, allerdings ohne Fall-through. Da sich ganze Objektgraphen matchen lassen, eröffnen sich vielfältige Möglichkeiten. Im unten stehenden Beispiel ist eine Konferenz mit einem bestimmten Namen zu suchen. Dafür wird eine case-Klasse erstellt, die sich in einer Fallunterscheidung verwenden lässt, da der Scala-Compiler sie entsprechend aufbereitet.

case class Conference(name: String)

val javaLand = Conference("JavaLand")

javaLand match {
case Conference("JavaLand") =>
println("JavaLand!")
case _ =>
println("Default Case")
}

Scala bietet mit Traits die Möglichkeit, einzelne Aspekte auszulagern und in Klassen unterzubringen (sogenannte Mix-ins). Dieses Vorgehen stellt eine Form der Mehrfachvererbung dar, die allerdings durch Delegation realisiert wird und so das Diamantenproblem [2] geschickt umgeht. Dabei werden die super-Aufrufe dynamisch gebunden und in eine lineare Abfolge gebracht. Die Bindung von super ist so zur Laufzeit eindeutig (nämlich immer an den nächsten Trait in der Hierarchie). Dank der Linearisierung lassen sich Mehrdeutigkeiten vermeiden und die Vorteile der Mehrfachvererbung, wie die feingranulare Aufteilung in Aspekte, nutzen. Im nächsten Code-Auszug werden die Merkmale "schwimmen" und "fliegen" in Traits ausgelagert und lassen sich dann einmischen. Im Beispiel ist ein Pinguin ein Vogel, der um den Aspekt "schwimmen" erweitert wird, ein Adler hingegen ist ein fliegender Vogel.

trait Schwimmen { def schwimme() {} }

trait Fliegen { def fliege() {} }

abstract class Vogel

class Pinguin extends Vogel with Schwimmen
class Adler extends Vogel with Fliegen

Die Kombination von objektorientierter und funktionaler Programmierung ist in Scala besonders ausgeprägt. Die funktionale Herangehensweise ermöglicht oft eleganten und einfachen Code und durch den Verzicht auf Seiteneffekte lässt sich der Code gut parallel ausführen. Das ausgefeilte Typsystem und die Vielzahl an Bibliotheken und Frameworks machen Scala zu einer empfehlenswerten Programmiersprache. An Scala gibt es allerdings auch Kritik, so werden stellenweise die Geschwindigkeit des Compilers und die Abwärtskompabilität bemängelt. Diese Probleme gehen die Scala-Entwickler allerdings aktiv an.

Die nächste Sprache verfolgt allein der Syntax nach einen anderen Ansatz: Clojure ist ein Lisp-Dialekt und wurde erstmals 2007 von Rich Hickey veröffentlicht. Die funktionale, dynamisch typisierte Programmiersprache kennt neben der JVM noch JavaScript und .NET als Zielplattformen. Die Syntax von Lisp-Programmen ist vergleichsweise einfach: Sie bestehen aus Listen in Präfix-Notation, das heißt, der Operator steht vor den Operanden. Im Code-Beispiel unten definiert defn eine Funktion mit einem Parameter. Das Minuszeichen wird per Konvention zur Worttrennung verwendet, statt der Camel-Case-Notation, die in Java üblich ist.

(defn say-hello [name]
(println (format "Hallo %s!" name)))

(say-hello "JavaLand")

Zur Notation der verschachtelten Listen kommen S-Expressions (Symbolic Expression) zum Einsatz. Sie lassen sich durch einen Binärbaum darstellen, wie in Abbildung 1 gezeigt. Die Besonderheit hierbei ist, dass Code und Daten dieselbe Repräsentation verwenden, was auch als Homoikonizität (Selbstabbildbarkeit) bezeichnet wird. Im folgenden Quelltextauszug repräsentieren Listen sowohl die Daten (die literale Liste) als auch den Code (die evaluierte Liste). Mit eval lassen sich die Daten ausführen. Die Funktion gibt es zwar auch in anderen Sprachen, allerdings wird in den meisten Fällen ein anderes Darstellungsformat für die Daten verwendet (z.B. als String), das erst einmal zu interpretieren ist.

Darstellung eines Ausdrucks als Binärbaum (Clojure). (Abb. 1)

Darstellung eines Ausdrucks als Binärbaum (Clojure). (Abb. 1)


'(println "Eine literale Liste")

(println "Eine evaluierte Liste (Form)")

(eval '(println "Eine literale Liste"))

Clojure bietet Kurzformen für oft genutzte Collections an. So lassen sich Vektoren, Sets und Maps schneller verwenden. Auch hier gilt, dass die eigentliche Darstellung anhand einer Liste erfolgt. Liste und Vektor unterscheiden sich anhand ihres Laufzeitverhaltens, folglich entscheidet der Anwendungsfall darüber, welche Variante der Entwickler einsetzen sollte. Im Zweifel wird empfohlen, einen Vektor zu verwenden – schon wegen der einfacheren Syntax gegenüber einer literalen Liste. Das folgende Beispiel zeigt die Collections und wie sich Symbole mit let an Datenstrukturen binden lassen. Die Bindung ist unveränderlich und nur im gegebenen Scope sichtbar.

["Ein" "Vektor"]
(vector "Ein" "Vektor")

#{"Ein" "Set"}

{:schluessel "Eine Map"}

(let [name "JavaLand"] (println name))

Rekursive Funktionen wie die unten gezeigte kommen in funktionalen Programmiersprachen häufig zum Einsatz. Im vorliegenden Fall soll sie alle Zahlen ab einem gegebenen Anfangswert verdoppeln und ruft sich dazu mit der nächsten Zahl selbst auf. Mit cons wird eine neue Liste mit dem verdoppelten Element am Anfang erzeugt. Auffällig ist, dass kein Abbruchkriterium angegeben ist. Beim Aufruf der Funktion sollen die Zahlen ab vier verdoppelt und mit take die ersten vier Stellen des Ergebnisses abgerufen werden.

Die etwas naive Implementierung einer rekursiven Funktion bricht allerdings beim Aufruf mit einem Stack-Overflow-Fehler ab, da, wie schon angedeutet, die Rekursion nicht beendet wird. Um den Fehler zu beheben, ließe sich der Funktion als zusätzlicher Parameter der Endwert übergeben, damit die Rekursion abbricht.

(defn double-from [n]
(cons (* n 2) (double-from (inc n))))

(println (take 4 (double-from 4)))

In Clojure gibt es allerdings eine viel elegantere Möglichkeit: Die Rekursion erfolgt erst dann, wenn tatsächlich auf das Element zugegriffen wird (lazy). Nachfolgend wurde der Aufruf von lazy-seq eingefügt und nun werden nur die Zahlen von vier bis sieben verdoppelt.

(defn double-from [n]
(cons (* n 2)
(lazy-seq (double-from (inc n)))))

(println (take 4 (double-from 4)))

Eine Besonderheit von Lisp-Dialekten, die viele interessante Möglichkeiten eröffnet, sind Makros. Durch das Code-as-Data-Prinzip sehen sie (fast) genauso aus wie Funktionen:

(defmacro dbg [body]
`(let [x# ~body]
(println "dbg:" '~body "=" x#)
x#))

(dbg (+ 1 2))
(println (macroexpand-1 '(dbg (+ 1 2))))

Das Makro gibt einen Ausdruck und das Ergebnis aus, was beim Debuggen hilfreich sein kann. Eine umfangreiche Beschreibung findet sich in einem entsprechenden Artikel [3]. Hier sollte nur deutlich werden, dass Makros relativ einfach zu implementieren sind und einen großen Mehrwert bieten.

Eine so einfache Syntax wie Clojure kann keine der hier vorgestellten Programmiersprachen bieten. Einfachheit und Klarheit ziehen sich durch die gesamte Sprache, die wenigen Konzepte und die Standardbibliothek. Wer sich auf Clojure einlässt, hat die Möglichkeit, eine neue Sichtweise auf Programmiersprachen zu bekommen. Inwieweit sie dann von den Arbeitskollegen geteilt wird, bleibt abzuwarten. Haben sich aber erstmal alle auf Clojure geeinigt, ist dies eine gute Wahl und einer erfolgreichen Entwicklung steht, zumindest aus Sicht der Programmiersprache, nichts entgegen.

Deutlich jünger als die bisher vorgestellten Sprachen ist Ceylon. Es wird seit 2011 von Red Hat entwickelt und ist vor Kurzem in Version 1.0 erschienen. Ceylon ist objektorientiert und statisch typisiert. Semikolons und Klammern sind bei Ceylon nicht optional, wie folgendes Hello-World-Programm zeigt.

void sayHello(String name) {
print("Hallo ``name``!");
}

void run() {
sayHello("JavaLand");
}

Die doppelten Backticks (``) sorgen für String Interpolation, run ist der Name der Einstiegsfunktion (ähnlich wie main in Java) und ein static-Schlüsselwort gibt es nicht. Stattdessen werden statische Methoden außerhalb einer Klasse definiert.

Eine Besonderheit von Ceylon ist das Modulssytem, das fester Bestandteil der Sprache ist und das Zusammenfassen von Paketen zu größeren Einheiten (Modulen) erlaubt. Im nächsten Beispiel ist eine Moduldefinition angegeben, die das Modul "de.olofsson" in Version 1.0.0 definiert und das Collection-Modul der Programmiersprache importiert.

// module.ceylon:
module de.olofsson "1.0.0" {
import ceylon.collection "1.0.0";
}

Braucht man in der Java-Welt ein Tool wie Maven oder Gradle, um Abhängigkeiten zu verwalten, ist eine entsprechende Option bereits in Ceylon enthalten. Ein Modul stellt unterschiedliche Pakete und Klassen zur Verfügung. Unten ist die Verwendung der Klasse HashMap aus dem Paket ceylon.collection gezeigt, das wiederum aus dem Modul ceylon.collection stammt. Soll ein Programmelement für andere Module sichtbar sein, muss es mit dem Schlüsselwort shared freigegeben werden.

// useMap.ceylon:
import ceylon.collection { HashMap }

Annotationen spielen in Ceylon eine zentrale Funktion. Anders als in Java sind sie ohne das führende @-Zeichen geschrieben und sehen damit aus wie Schlüsselwörter. Der Compiler prüft die Annotationen, dadurch lassen sich beispielsweise fehlerhafte Verweise in der Dokumentation vermeiden.

Richtig lassen sich Annotationen wie folgt verwenden:

import ceylon.language.meta
{ annotations }

"Eine Funktion mit Annotationen."
by ("Simon Olofsson")
void annotationsDemo() {

value a = annotations(`DocAnnotation`,
`function annotationsDemo`);
}

Über der Funktion annotationsDemo stehen die Dokumentationsannotationen, die sie wieder ausliest. Dabei referenzieren Ausdrücke in Backticks das Metamodell von Ceylon, das hilfreich ist, um auf Programmelemente zuzugreifen. DocAnnotation ist der Typ des Dokumentations-Strings oberhalb der Funktion.

Das Typsystem von Ceylon ist recht ausgefeilt, so gibt es Vereinigungstypen, die einen Typen definieren, der alle angegebenen Typen enthält. Dies ist nützlich, um zu deklarieren, dass eine Variable einen Nullwert enthalten könnte.

List<String> l1 = ["Eins"];

String? s1 = l1.get(0);
String|Null s2 = l1.get(0);

List<String|Integer> l2 = ["Eins", 1];

Der hier gezeigte Vereinigungstyp ist entweder ein String oder Null. Da dies in der Praxis häufig vorkommt, gibt es mit String? eine Abkürzung. Die darunter angegebene Liste kann Zeichenketten und Ganzzahlen enthalten. Das Gegenstück sind die Schnitttypen (Typ A und B), die allerdings seltener gebraucht werden.

Wer sich gerne auf Neues einlässt, trifft mit Ceylon eine gute Wahl. Die noch relativ junge Programmiersprache bricht mit einigen Strukturen der Java-Welt, was sich nicht nur an umbenannten Schlüsselwörtern festmacht. Das integrierte Modulkonzept ist anfangs vielleicht etwas zu mächtig, erweist aber ab einer bestimmten Programmgröße gute Dienste. Auch die häufige Nutzung von Annotationen und das ausgeklügelte Typsystem machen einen sinnvollen Eindruck. Ceylon als einzige Programmiersprache einzusetzen ist vielleicht verfrüht, aber Komponenten, die nicht im Mittelpunkt der Anwendung stehen, lassen sich schon in Ceylon realisieren.

Kotlin wird, wie Ceylon, seit 2011 entwickelt und ist statisch typisiert. Urheber ist das Software-Unternehmen JetBrains, das bisher vor allem durch Entwicklungsumgebungen wie IntelliJ IDEA Bekanntheit unter Entwicklern erlangte. Ebenso wie in Scala kommt in Kotlin die Postfix-Notation für Typangaben zum Einsatz. Semikolons sind optional, und Funktionen werden mit fun definiert.

fun sayHello(name: String) {
println("Hallo $name!")
}

fun main(args: Array<String>) {
sayHello("JavaLand")
}

Smart-Casts sind ein Konzept in Kotlin, bei dem der Compiler testet, ob bereits eine Typprüfung stattgefunden hat und automatisch eine Typumwandlung (Cast) durchführt, sollte es nötig sein.

fun double(a: Any): Int {
if (a is Int) {
return a * 2
}
return 0
}

Im obigen Quelltextauszug weiß der Compiler, dass bereits getestet wurde, ob ein a ein Int ist und eine entsprechende Umwandlung sicher durchgeführt werden kann. In Java wäre nach dem Test mit instanceof das Einfügen eines manuellen Cast nötig.

Der Satz "Der einen Sprache Muster ist der anderen Sprache Konstrukt." verdeutlicht, dass Entwurfsmuster, die in bestimmten Sprachen eingesetzt werden, in anderen Sprachen als Programmkonstrukte vorhanden sind. So verhält es sich mit der Delegation. In vielen Sprachen ist sie ein Entwurfsmuster, um Methodenaufrufe an andere Objekte weiterzuleiten. Kotlin enthält Delegation direkt. Dazu ist anzugeben, welche Methoden an welches Objekt zu delegieren sind. Der nächste Ausschnitt leitet alle Methoden von List an das Objekt l. So lassen sich die Methoden aufrufen, als ob sie in MyList implementiert wären.

class MyList(
l: List<String>): List<String> by l {

fun printSize() {
println(size())
}
}

Oft kommt es vor, dass die Klassen einer Bibliothek zu erweitern sind, ohne dass sich ihr Code modifizieren lässt. Kotlin bietet mit Extension Functions die Möglichkeit, einen Typen um bestimmte Funktionen zu erweitern. Im nächsten Beispiel wird die Schnittstelle List um die Funktion printFirst erweitert, sodass sie sich anschließend auf allen Listen aufrufen lässt. Damit klar ist, welche Erweiterungsmethoden zur Verfügung stehen, sind sie immer zu importieren.

fun List<String>.printFirst() {
println(first)
}

fun testPrintFirst() {
val list = listOf("Eins", "Zwei")
list.printFirst()
}

Eine junge Programmiersprache mit sehr guter IDE-Unterstützung findet man natürlich bei einem Hersteller von Entwicklungsumgebungen. Das ist jedoch bei weitem nicht die einzige Besonderheit von Kotlin. Viele raffinierte Details und Verbesserungen machen aus der Sprache ein ansehnliches Gesamtpaket. Hier gilt Ähnliches wie bei Ceylon: die komplette Entwicklung hierauf umzustellen, ist momentan recht gewagt.

Fantom wird seit 2005 entwickelt und ist eine objektorientierte Programmiersprache, die sich wahlweise statisch oder dynamisch typisieren lässt. Sie ist unter dem Namen fan gestartet, wurde aber umbenannt, um die Auffindbarkeit in Suchmaschinen zu verbessern. Semik olons sind optional, und die Standardsichtbarkeit ist public, ansonsten sollte das folgende Hello-World-Beispiel verständlich sein.

class HelloWorld {

static Void sayHello(Str name) {
echo("Hallo $name!")
}

static Void main() {
sayHello("JavaLand")
}
}

Die von Fantom unterstützte dynamische Programmierung lässt sich einsetzen, um per Duck Typing auf Methoden zuzugreifen, die statisch typisiert nicht zugänglich wären. Nachfolgend soll eine Nummer mit der Methode mult verdoppelt werden, allerdings ist mult nicht für Num sondern nur für Int definiert. Mit dem Pfeiloperator umgeht der Entwickler die Typisierung, und der Methodenaufruf erfolgt per Reflection, was deutlich langsamer ist.

Zur dynamischen Programmierung lässt sich Obj als Wildcard nutzen. Ist der Typ einer Variablen Obj, fügt der Compiler im Zweifelsfall einen Cast ein – i1 und i2 sind also identisch.

class DynamicProgramming {
static Void main() {

Num n := 2
// n.mult(2)
// Unknown method 'sys::Num.mult'
n->mult(2)

Obj o := "4"
Int i1 := Int.fromStr((Str) o)
Int i2 := Int.fromStr(o)
}
}

Das von Fantom genutzte Serialisierungsformat ist einfach zu verstehen, und die serialisierten Objekte sind wiederum gültige Fantom-Objekte:

@Serializable class Conference {
Str? name
Int year

static Void main() {
javaLand := Conference {
name = "JavaLand"
year = 2014
}
Env.cur.out.writeObj(javaLand)
}
}

Der Code-Ausschnitt zeigt eine Klassendefinition, die Initialisierung der Klasse und die Serialisierung. Die serialisierte Form ist analog zur Initialisierung.

Fantom unterstützt Aktoren zur asynchronen Kommunikation über Nachrichten. Sie sind kein exklusiv in Fantom auftretendes Feature, sondern in vielen Programmiersprachen und Bibliotheken verfügbar. Mit dem folgenden Quelltext-Ausschnitt soll das Programm einen Aktoren-Pool erzeugen, der die Aktoren verwaltet. Anschließend wird ein Aktor mit einer anonymen Funktion definiert, der einen freundlichen Gruß an den Absender zurücksendet. Nun lässt sich dem Aktor eine Nachricht schicken. Der get-Aufruf blockiert bis zum Empfang der Antwort. In einem realistischeren Anwendungsfall könnten hier weitere Berechnungen stattfinden. Mit Aktoren lässt sich die parallele Verarbeitung von Aufgaben vergleichsweise einfach implementieren.

using concurrent

class Actors {
static Void main() {

p := ActorPool()
actor := Actor(p) { "Hallo $it!" }

echo(actor.send("JavaLand").get)
}
}

Fantom ist eine kleine und feine Programmiersprache mit einer sympathischen Community, die gern weiterhilft. Für den unternehmensweiten Einsatz fehlt eventuell die Unterstützung eines namhaften Herstellers. Aber die gelungene Kombination von statischer und dynamischer Programmierung ist einen Blick wert. Vielleicht findet sich auch hier ein geeignetes Seitenprojekt für den Einstieg in die Sprache.

In diesem Artikel wurden sechs Programmiersprachen mit ihren jeweiligen Eigenheiten dargestellt. Wann welche Sprache eingesetzt wird, ist stark vom Anwendungsfall, dem Vorwissen und den sonstigen Anforderungen (zum Beispiel an Enwicklungsumgebungen oder Build-Tools) abhängig. In jedem Fall bietet jede Sprache interessante Konzepte und ist in ihrer Gesamtheit einzigartig. Auch wenn man nicht das nächste große Projekt mit einer neuen Sprache umsetzten möchte, lohnt sich doch die Beschäftigung mit der ein oder anderen Sprache, um neue Sichtweisen zu eröffnen. Wer die jüngere Entwicklung von Java verfolgt, wird bemerken, dass einzelne Neuerungen durchaus von den hier vorgestellten Sprachen übernommen wurden.

Simon Olofsson
arbeitet als leitender Entwickler bei der ContentManagement AG in Köln. Dort ist er für die Entwicklung zahlreicher moderner Webapplikationen verantwortlich und setzt dazu gerne aktuelle Technologien ein.
(jul [4])


URL dieses Artikels:
https://www.heise.de/-2281060

Links in diesem Artikel:
[1] https://www.heise.de/hintergrund/Ein-Ueberblick-ueber-Java-Alternativen-fuer-den-industriellen-Einsatz-2074554.html
[2] http://de.wikipedia.org/wiki/Diamond-Problem
[3] http://www.jayway.com/2011/03/13/dbg-a-cool-little-clojure-macro/
[4] mailto:jul@heise.de