Mit Dart will Google eine moderne Alternative zu Javascript etablieren und hat dazu interessante Features anderer Sprachen aufgegriffen. Dart soll vor allem in Browsern laufen, lässt sich aber auch auf der Kommandozeile sowie auf Servern als PHP-Ersatz nutzen. Also ein Volltreffer?
Programmcode in Googles Sprache Dart [1] liest sich wie eine Mischung aus Javascript und Java, gewürzt mit einer Prise Scala. Diese Anleihen haben einen Hintergrund: Die Programmiersprache entstand aus dem Wunsch, einige Probleme und Altlasten von Javascript zu eliminieren. So sollen Dart-Programme schneller ablaufen, eine höhere Sicherheit bieten, auf allen internetfähigen Geräten arbeiten und zugleich für größere Projekte geeignet sein [2]. Wer in den genannten Sprachen schon mal programmiert hat, dürfte sich zudem auch schnell in Dart zurechtfinden – und das sind fast alle Webprogrammierer.
In Dart geschriebene Programme sollen in einer speziellen virtuellen Maschine direkt im Browser laufen. Das Dart-Projekt stellt dazu auf seinen Seiten eine Referenzimplementierung bereit, die sogar in der Lage ist, Dart-Programme direkt auf der Kommandozeile beziehungsweise auf einem Internetserver auszuführen. Dart eignet sich damit prinzipiell als PHP-Ersatz. Das Leben erleichtern zudem einige nützliche Werkzeuge wie der auf Eclipse basierende Dart Editor (Abbildung 1).
Das alles klingt verlockend, wäre da nicht ein kleiner Haken: Die Sprache befindet sich derzeit noch in einem frühen Entwicklungsstadium und taugt somit nicht für den produktiven Einsatz. Die Väter von Dart haben ihre Spezifikation bewusst vorzeitig veröffentlicht, um möglichst viele Verbesserungsvorschläge zu sammeln und gleichzeitig die Akzeptanz zu erhöhen. Gerade auch deshalb steht die Sprachspezifikation unter einer liberalen Creative-Commons-Lizenz, der vom Projekt veröffentlichte Programmcode unter der Google BSD License [3]. Zum Redaktionsschluss war die Dart-Version 0.08 aktuell, die auch dem folgenden Überblick zugrunde liegt.
main() und Klassen
Ein erstes, einfaches Dart-Programm zeigt Listing 1. Insbesondere für Javascript-Programmierer dürfte »main()« neu sein. Wie in Java bildet diese Funktion den Startpunkt des Dart-Programms. Javascript-Programmierer müssen noch ein zweites Mal schlucken: Dart ist eine durch und durch objektorientierte Sprache. Listing 2 zeigt die Deklaration einer Klasse namens »Auto« . Diese definiert zunächst die neue Variable »farbe« . Wie das Schlüsselwort »var« andeutet, können Variablen zu jeder Zeit beliebige Werte aufnehmen – Dart ist wie Javascript eine typenlose Sprache.
Listing 2
Eine einfache Klasse
01 class Auto {
02 var farbe;
03
04 Auto() {
05 this.farbe = "Blau";
06 }
07
08 Auto.lackieren(this.farbe);
09 }
10
11 main() {
12 Auto ferrari = new Auto.lackieren("Rot");
13 Auto bmw = new Auto();
14 print(ferrari.farbe); // gibt Rot aus
15 print(bmw.farbe); // gibt Blau aus
16 }
Listing 1
“Hello World” in Dart
01 main() {
02 // Ein kleiner Kommentar
03 print("Hallo Welt!");
04 }
Es folgt der Konstruktor »Auto()« , der den gleichen Namen wie die Klasse tragen muss. »this« steht wie in Java für das eigene Objekt, »this.farbe« stellt folglich sicher, dass der Wert nicht in einer globalen oder gar neuen Variablen landet.
Benannte Konstruktoren
Die achte Zeile zeigt zwei Besonderheiten von Dart. In typisierten Sprachen wie Java kann der Entwickler mehrere Konstruktoren mit unterschiedlichen Parametern vorgeben (überladen). Die Sprache ruft dann bei der Erzeugung eines Objekts automatisch den passenden Konstruktor auf. Bei typenlosen Sprachen wie Dart ist diese Wahl nicht so einfach. Abhilfe schaffen benannte Konstruktoren. In Listing 2 erhält der zweite Konstruktor den Namen »lackieren« . Möchte man später beim Anlegen eines konkreten »Autos« in der »main()« -Funktion genau diesen aufrufen, gibt man dort den Namen mit an:
Auto ferrari = new Auto.lackieren("Rot");
Ein schlichtes »new Auto()« ruft hingegen den normalen Konstruktor auf.
Als zweite Besonderheit verwendet der Konstruktor »Auto.lackieren()« noch eine Kurzschreibweise: Den ihm übergebenen Wert weist Dart direkt der Variablen »farbe« zu. Wer mag, darf natürlich auch die Langfassung verwenden:
Auto.lackieren(var einefarbe) {
this.farbe = einefarbe;
}
Dass Variablen beliebige Werte aufnehmen, ist zwar bequem, verleitet Entwickler in der Praxis aber zum Schludern. So entstehen Fehler, die zu ergründen oft aufwändig ist. Deshalb darf der Programmierer optional den Typ einer Variablen angeben (»String farbe« ).
Sollte das Dart-Programm im weiteren Verlauf versuchen in »farbe« eine Zahl zu speichern, erzeugt die virtuelle Maschine eine Warnung. Wohlgemerkt eine Warnung, keinen Fehler – das Programm läuft einfach weiter. Dieses Verhalten macht Programmierer auf Zuweisungsfehler aufmerksam und den Code besser lesbar, während Benutzer der Anwendung keinen urplötzlichen Programmabsturz fürchten müssen.
Neben Zeichenketten (»String« ) kennt Dart derzeit noch die Typen aus Tabelle 1. Die sind übrigens intern allesamt auch Objekte. Listen und Maps darf man sogar mit »new« anlegen:
var autos = new List();
Variablen, die noch keinen Wert zugewiesen bekommen haben, besitzen den speziellen Wert »null« .
Ähnlich wie PHP kann Dart den Inhalt von Variablen in andere Strings einsetzen. Der Code
String name = "Herr Meier";
print ("Hallo ${name}");
würde »Hallo Herr Meier« ausgeben. Innerhalb der Klammern »{}« dürfen sogar ganze Ausdrücke beziehungsweise Funktionsaufrufe stehen.
Strichfassung
Eine einzelne Funktionsdefinition wie beispielsweise
int quadrat(int zahl) { return zahl*zahl; }
darf der Dart-Anwender mit »=>« abkürzen:
int quadrat(int zahl) => zahl*zahl;
»=> e« steht dabei für »{ return e; }« . In Dart darf man zudem eine Funktion an eine andere übergeben, was besonders in »for« -Schleifen nützlich ist.
Die Funktion »sagHallo()« in Listing 3 setzt den ihr übergebenen String hinter »Hallo« und gibt das Ergebnis auf dem Bildschirm aus. Anschließend erstellt die zweite Zeile eine Liste mit drei Namen, die »forEach« nacheinander in die ihr übergebene Funktion schiebt – in diesem Fall also »sagHallo()« . Der Dreizeiler gibt folglich »Hallo Tim« , »Hallo Klaus« und »Hallo Hans« aus. Da »sagHallo()« nur in »namen.forEach()« auftaucht, kann man die Funktion auch direkt dort deklarieren und dabei gleich noch auf ihren Namen verzichten, wie Listing 4 zeigt.
Listing 4
Funktion übergeben (II)
01 List namen = ["Tim", "Klaus", "Hans"];
02 namen.forEach( (String name) => print("Hallo ${name}") );
Listing 3
Funktion übergeben (I)
01 sagHallo(String name) => print("Hallo ${name}");
02 List namen = ["Tim", "Klaus", "Hans"];
03 namen.forEach(sagHallo);
Das ist zwar sehr kompakt, bei komplexeren Funktionen geht allerdings schnell die Übersicht flöten. Zur Steuerung des Kontrollflusses stehen ansonsten die üblichen Verdächtigen »for« , »if« , »switch« und »while« bereit, die genau wie ihre Pendants in Java beziehungsweise Javascript funktionieren. Zudem lassen sich wie in Java über Exceptions elegant Fehler abfangen.
Klassisches
Von Java hat Dart auch die Interfaces geerbt. Implementiert eine Klasse ein Interface, sichert sie zu, die im Interface vorgegeben Funktionen anzubieten. Ein Beispiel samt Vererbung zeigt Listing 5. Eine Klasse kann mehrere Interfaces implementieren, aber immer nur von genau einer Klasse erben (sie also via »extends« erweitern).
Listing 5
interfaces und extend
01 interface Flaeche {
02 int flaechenInhalt();
03 }
04
05 class Quadrat implements Flaeche {
06 int breite;
07 Quadrat(int this.breite);
08 int flaechenInhalt() => breite*breite;
09 }
10
11 class Rechteck extends Quadrat {
12 int _hoehe;
13 Rechteck(int this._hoehe, int br) : super(br);
14 int flaechenInhalt() => breite * _hoehe;
15 }
16
17 main() {
18 Quadrat q = new Quadrat(3);
19 Rechteck r = new Rechteck(2,3);
20 print(q.flaechenInhalt());
21 print(r.flaechenInhalt());
22 }
In Listing 5 sind alle Variablen und Funktionen öffentlich zugänglich – mit einer Ausnahme: »_hoehe« . Beginnt der Name einer Variablen oder einer Funktion mit einem Unterstrich, gilt sie als privat. Nur aus der Klasse selbst kann der Programmierer dann noch darauf zugreifen. Im Beispiel führt das dazu, dass sich die Höhe eines Rechtecks nachträglich nicht mehr verändern lässt.
Design Patterns
Entwurfsmuster [4] gehören schon seit Jahren zum Repertoire des professionellen Programmierers. Es überrascht nicht, dass Dart darauf setzt. Das Factory Pattern beispielsweise ist bereits Bestandteil der Sprache: Stellt der Entwickler einem Konstruktor das Schlüsselwort »factory« voran, erzeugt Dart nicht automatisch selbst ein Objekt dieser Klasse, sondern überlässt das dem Konstruktor. In diesem kann der Programmierer dann beispielsweise in einem Cache nachsehen, ob schon ein passendes Objekt existiert – genau wie in Listing 6: »nocheinauto« verweist hier auf dasselbe Objekt wie »einauto« . Aufgrund des Schlüsselworts »static« existiert »garage« nur ein einziges Mal, alle »Auto« -Objekte greifen auf dieselbe Variable zu.
Listing 6
Factory Pattern
01 class Auto {
02 String gefertigtvon;
03 static Map garage;
04
05 factory Auto(String hersteller) {
06 if(Auto.garage == null) Auto.garage=new Map();
07
08 if(Auto.garage.containsKey(hersteller)!=null) return Auto.garage[hersteller];
09 else {
10 Auto neuesauto = new Auto.kaufen(hersteller);
11 Auto.garage[hersteller] = neuesauto;
12 return neuesauto;
13 }
14 }
15
16 Auto.kaufen(this.gefertigtvon);
17 }
18
19 main() {
20 var einauto = new Auto("Ferrari");
21 var nocheinauto = new Auto("Ferrari");
22 }
Das Factory Pattern lässt sich zudem noch mit Interfaces kombinieren. Dazu weist der Programmierer einem Interface eine Standard-Factory-Klasse zu, die wiederum zum Interface passende Objekte zurückliefert. Listing 7 zeigt dazu ein Beispiel. Das Interface legt dabei die Signatur des Konstruktors fest, der dann zum Interface passende Objekte ausgibt. Die »AutoFabrik« liefert abhängig vom ihr übergebenen »hersteller« entweder ein »Rennwagen« – oder ein »Mittelklasse« -Objekt. Bei der Instanzierung in »main()« sieht es dann so aus, als würde man direkt ein »Auto« anlegen.
Listing 7
Kombination von interface und factory
01 interface Auto default AutoFabrik {
02 Auto(hersteller);
03 final hersteller;
04 }
05
06 class AutoFabrik {
07 factory Auto(hersteller) {
08 if (hersteller == "Ferrari") {
09 return new Rennwagen(hersteller);
10 }
11 return new Mittelklasse(hersteller);
12 }
13 }
14
15 class Rennwagen implements Auto {
16 Rennwagen(this.hersteller);
17 String hersteller;
18 }
19
20 class Mittelklasse implements Auto {
21 Mittelklasse(this.hersteller);
22 String hersteller;
23 }
24
25 main() {
26 print(new Auto("Ferrari") is Rennwagen);
27 print(new Auto("VW") is Rennwagen);
28 }
Wie in Listing 7 ebenfalls zu sehen ist, prüft das Schlüsselwort »is« , ob ein Objekt von einem bestimmten Typ ist. Das Schlüsselwort »final« vor »hersteller« sorgt übrigens dafür, dass sich der Variablen nur genau einmal bei ihrer Initialisierung ein Wert zuweisen lässt.
Dart enthält die aus Java bekannten generischen Typen alias Generics. C++-Programmierer kennen das Konzept als Templates. Mit ihnen lassen sich schnell Container für beliebige Objekte schaffen. Die eingebauten Listen und Maps sind bereits von Haus aus generische Typen. So erzeugt etwa »List<Auto>« eine Liste für Auto-Objekte (Listing 8).
Listing 8
Generische Typen
01 main() {
02 List<Auto> autoliste = new List<Auto>();
03 autoliste.add(new Auto("Ferrari"));
04 Auto einauto = autoliste[0];
05 }
Da Variablen beliebige Inhalte aufnehmen, sperrt sich Dart allerdings auch nicht gegen Folgendes:
List<Auto> autoliste = new List<Auto>(); List<LKW> lkwliste = autoliste;
Dieser Versuch fliegt dem Entwickler aber später vielleicht um die Ohren, wenn er etwas mit den vermeintlichen LKWs aus der »lkwliste« anstellen möchte.
Nebenläufigkeit
Parallel zu erledigende Aufgaben lagert der Programmierer normalerweise in je einen separaten Thread aus und versucht mit viel Hirnschmalz und Verwaltungsaufwand deren Zwischenergebnisse zusammenzuführen. Dem nimmt Dart den Schrecken: Ein von der Basisklasse »Isolate« abgeleitetes Objekt läuft auf Wunsch isoliert neben dem Hauptprogramm in einem eigenen Thread.
Um miteinander Zwischenergebnisse auszutauschen, können sich solche Isolates gegenseitig Nachrichten schicken. Die landen zunächst in einer Warteschlange, bis sie das empfangende Isolate über einen so genannten Port abholt. Dieses Konzept lehnt sich stark an das Aktoren-Modell aus Erlang beziehungsweise Scala an. Listing 9 zeigt ein komplettes Beispiel dafür.
Listing 9
Kommunikation mit einem Isolate
01 class Empfaenger extends Isolate {
02 main() {
03 port.receive((nachricht, replyTo) {
04 if (nachricht == null) port.close();
05 else print("Empfange: ${nachricht}");
06 });
07 }
08 }
09
10 main() {
11 new Empfaenger().spawn().then((port) {
12 for (var nachricht in ['Dies', 'ist', 'ein', 'Test']) {
13 port.send(nachricht);
14 }
15 port.send(null);
16 });
17 }
Zunächst erstellt das Beispielprogramm ein neues »Empfaenger« -Objekt, das es via »spawn()« in einen eigenen Thread verfrachtet. Anschließend sendet es dem Objekt nacheinander vier Wörter. Sobald das Objekt ein Wort empfängt, schreibt es dieses auf den Bildschirm. Das Isolate bekommt übrigens mit »replyTo« immer einen Port zum Objekt übergeben, das ihm die Nachricht geschickt hat. Damit kann es dann direkt seinem Auftraggeber antworten.
Isolates laufen in getrennten Speicherbereichen. Das hat den angenehmen Nebeneffekt, dass die Garbage Collection jedes Isolate individuell behandeln kann. Im Gegenzug muss die virtuelle Maschine die Nachrichten zwischen den Isolates kopieren. Künftig sollen Isolates sogar unterschiedliche Dart-Bibliotheken in verschiedenen Versionen nutzen können.
DOM-Zugriff
Da Dart Javascript ersetzen soll, muss der Anwender mit der Sprache auch auf den DOM-Baum einer Webseite zugreifen können. Dazu bringt Dart eine eigene Bibliothek mit, die sich via
#import("dart:html");
einbinden lässt. Anschließend gelangt der Programmierer mit dem folgenden Ausdruck an ein »<div>« mit der ID »menue« :
document.query("#menue");
Elemente lassen sich also über CSS-Selektoren finden, ganz ähnlich wie bei Jquery. Mit weiteren Klassen und Funktionen bauen Dart-Programme unter anderem HTTP-Verbindungen auf (Stichwort Ajax), verarbeiten Json-Daten und greifen sogar auf das Dateisystem zu. Eine komplette Aufstellung liefert die API-Referenz [5].
Sei kein Frosch!
Da aktuelle Browser noch nichts mit Dart-Programmen anfangen können, entwickelte das Dart-Projekt den Compiler Dartc. Er übersetzt die Dart-Programme in Javascript-Code. Dartc stand jedoch bald in dem Ruf, extrem große Javascript-Programme zu generieren, die deshalb nur ziemlich langsam laufen. Abhilfe soll ein neuer Compiler namens Frog schaffen. Er ist selbst in Dart geschrieben und generiert wesentlich kompakteren Javascript-Code.
Frog und eine virtuelle Maschine für die Kommandozeile (Abbildung 2) sind im Dart-SDK enthalten, das man kostenlos auf der Projekthomepage bekommt [6]. Der Anwender muss nur das passende Zip-Archiv herunterladen, entpacken und dann das eigene Dart-Programm »test.dart« entweder mit »frog« aus dem »bin« -Verzeichnis übersetzen:

Abbildung 2: Das Dart-SDK bringt neben einem Compiler auch eine virtuelle Maschine für die Kommandozeile mit.
./frogc --enable_type_checks test.dart
Oder er führt es gleich mit der virtuellen Maschine aus:
./dart --enable_type_checks test.dart
Der Parameter »–enable_type_checks« sorgt dafür, dass der Compiler beziehungsweise die virtuelle Maschine die Typprüfung aktiviert. Wer nicht gleich das SDK einsetzen möchte, kann seine Programme auch direkt auf der Homepage des Dart-Projekts von dem so genannten Dartboard ausführen lassen ([7], Abbildung 3).

Abbildung 3: Installation nicht erforderlich: Das Online-Tool Dartboard erlaubt das Testen von kleineren Dart-Programmen in jedem modernen Webbrowser.
Dartboard bindet die meisten Bibliotheken der Sprache von Haus aus ein, während der Programmierer dies bei der Arbeit mit der Kommandozeilen-VM selbst per Import-Statement erledigen muss. Und dann hat Google auch noch Dartium [8] im Angebot, eine Spezialfassung des Browsers Chromium mit eingebauter virtueller Maschine für Dart.
Die komplette Spezifikation der Dart-Sprache gibt es unter [9] zur Ansicht, der Quellcode einiger größerer Dart-Beispiele wartet unter [10]. Von Dartium abgesehen ignorieren gegenwärtig noch alle Browser Dart. Verübeln kann man es ihnen nicht, schließlich ist die Programmiersprache alles andere als fertig. Gleichzeitig sieht sich Dart einiger Kritik ausgesetzt. Die meisten halten die Unterstützung einer weiteren Websprache für überflüssig und kontraproduktiv.
Ausblick
Die Zukunft von Dart hängt nicht zuletzt davon ab, wie ernst es Google mit ihr meint. Das Unternehmen hätte die Macht, die Sprache durchzudrücken: Zieht sie erst einmal in alle Android-Geräte und den Chrome-Browser ein, dürften die anderen Browserhersteller nicht mehr so einfach um sie herumkommen. Doch besteht die Gefahr, dass sich ein Lager der Verweigerer bilden könnte, was wiederum eine erneute Fragmentierung im Web zur Folge hätte. Doch selbst die Kritiker müssen zugeben, dass sich Dart-Programme einfacher lesen und verstehen lassen als ihre Javascript-Pendants. Dank der echten Objektorientierung sind zudem größere und besser wartbare Webanwendungen möglich.
Eine einzige Sprache für Client und Server – das klingt zudem verlockend: Webprogrammierer bräuchten nicht ständig zwischen Javascript und beispielsweise PHP zu wechseln. Selbst wer nicht aus dem Java-Lager kommt, schafft schnell den Um- und Einstieg. Schließlich ist die Sprache im Gegensatz zu Java offen organisiert, jeder Nutzer kann eigene Verbesserungsvorschläge einreichen. Zumindest bis jetzt. (mhu)
Infos
- Dart-Projektseite: http://www.dartlang.org
- Designziele von Dart: http://www.dartlang.org/docs/technical-overview/#goals
- Google BSD License: http://code.google.com/intl/de-DE/google_bsd_license.html
- Wikipedia-Eintrag zu Entwurfsmustern:http://de.wikipedia.org/wiki/Entwurfsmuster
- Dart API Reference: http://api.dartlang.org
- Dart-SDK: http://www.dartlang.org/docs/getting-started/sdk/
- Dartboard: http://try.dartlang.org
- Browser Dartium: http://www.dartlang.org/dartium/
- Dart-Sprachsyntax: http://www.dartlang.org/docs/spec/
- Dart-Programmbeispiele: http://www.dartlang.org/samples/
- Listings zu diesem Artikel: https://www.linux-magazin.de/static/listings/magazin/2012/06/dart/







