Aus Linux-Magazin 05/2017

Coffeescript, Dart, Elm und Typescript

© belchonock, 123RF

Javascript ist der Stoff, aus dem der Client-Teil vieler interaktiver Webanwendungen gemacht ist, schleppt aber Altlasten mit. Gegen die schreiben die Macher der vier vorgestellten Skriptsprachen an.

Anwendungen, deren interaktiver Teil im Browser läuft, verschafften Javascript in jüngster Zeit ein Comeback. Dabei erblickte die Skriptsprache bereits Mitte der 90er Jahre das Licht der Welt. Ihre Macher stülpten der anfangs recht simplen Skriptsprache im Laufe der Zeit immer weitere Konstrukte über. Ein Paradebeispiel ist die von vielen Entwicklern gehasste Prototyp-basierte Objektorientierung, die häufig unverstanden und ungenutzt blieb.

Erst 2015 erhielt die mittlerweile als ECMA-Script standardisierte Programmiersprache eine optionale Klassen-basierte Objektorientierung. Gegenüber Java, C++ & Co. fehlen ihr jedoch weiterhin Features [1]. Obendrein bieten Javascript beziehungsweise ECMA-Script nach wie vor keine Typprüfung an, was in der Praxis immer wieder gern zu Ausnahmefehlern führt.

Gleich mehrere Skriptsprachen möchten Javascript daher gern ablösen oder zumindest in puncto Programmierung vereinfachen. Zu den besonders weit verbreiteten und beliebten gehören derzeit Coffeescript [2], Googles Dart [3], Elm [4] sowie Microsofts Typescript [5]. In einem Vergleich zeigt der Artikel, was die vier Kandidaten voneinander unterscheidet und in welchem Bereich sie Javascript womöglich den Rang ablaufen.

Coffeescript

Jeremy Ashkenas veröffentlichte 2009 die erste Version von Coffeescript [2]. Seine Skriptsprache erfindet das Rad nicht neu, sondern ergänzt Javascript einfach um weitere Konstrukte. Der Vorteil: Entwickler verwenden existierende Javascript-Bibliotheken wie Jquery [6] weiter. Bei Redaktionsschluss lag Coffeescript in der Version 1.12.4 vor, die nur ECMA-Script 5 und einige Teile von ECMA-Script 2015 unterstützt. Erst das kommende Coffeescript 2.0.0 wird weitere Elemente von ECMA-Script 2015 implementieren, insbesondere die dort eingeführten Klassen.

Der Compiler steht unter der MIT-Lizenz und benötigt eine Javascript-Laufzeitumgebung wie etwa Node.js. Für zahlreiche Texteditoren, zum Beispiel Emacs und Gedit, existieren Coffeescript-Plugins. Wer Coffeescript erst einmal ausprobieren möchte, kann dies über einen Online-Editor [7] auf der Projektseite tun (Abbildung 1).

Abbildung 1: Auf der Coffeescript-Homepage d&uuml;rfen Entwickler unter <code>Try Coffeescript</code> auf der linken Seite Quellcode eintippen und per <code>Run</code> ausf&uuml;hren lassen. Rechts erscheint der &uuml;bersetzte Javascript-Code.

Abbildung 1: Auf der Coffeescript-Homepage dürfen Entwickler unter »Try Coffeescript« auf der linken Seite Quellcode eintippen und per »Run« ausführen lassen. Rechts erscheint der übersetzte Javascript-Code.

Ein vom Coffeescript-Team bereitgestellter Compiler übersetzt Coffeescript-Code in das Javascript-Pendant, das dann wiederum in jedem Browser läuft. Der erzeugte Javascript-Code soll schneller arbeiten als ein per Hand geschriebenes Äquivalent.

Coffeescript erlaubt dem Entwickler eine stark verkürzte Notation. Unter anderem darf er das »var« vor Variablendeklarationen und das Semikolon am Ende eines Ausdrucks weglassen. Kommentare beginnen wie in Shellskripten mit dem Hash »#«, die zwei Schrägstriche »//« führen hingegen eine Ganzzahl-Division aus. Längere Strings und reguläre Ausdrücke darf der Entwickler über mehrere Zeilen verteilen. Coffeescript erlaubt es auch, Arrays schnell zu zerschneiden:

start = countdown[0..2]

In diesem Fall enthält »start« nur die ersten drei Elemente von »countdown«. Entwickler dürfen Funktionen mit dem »->«-Operator erstellen und Parameter mit Vorgabewerten belegen:

mult = (x = 1) -> x * x

Anstelle der geschweiften Klammern kennzeichnen Einrückungen Anweisungsblöcke, wie es Python vormacht. Listing 1 definiert auf diese Weise ein Objekt »Punkt« mit zwei Properties und einer Methode »zeichne()«. Mit dem so genannten Splats-Operator »…« lassen sich zudem einer Funktion beliebig viele Parameter übergeben. Das »?« prüft, ob die Variable existiert.

Listing 1

Einfaches Objekt in Coffeescript

01 Punkt =
02         x: 1
03         y: 2
04         farbe: "rot"
05         zeichne: () -> alert(this.x + "," + this.y + "," + this.farbe);
06         faerbe: (a, b...) ->
07                 existiert = true if this.farbe?
08                 if existiert == true
09                         this.farbe=a
10                 else alert("Keine Farbe");
11
12 Punkt.faerbe("gruen");
13 Punkt.zeichne();

Für einfache Bedingungen existiert die Abkürzung »Ergebnis = Wert if Bedingung«. Da der Vergleichsoperator »==« in Javascript oft zu Fehlinterpretationen führt, übersetzt ihn der Coffeescript-Compiler automatisch in »===«. Zudem gibt es Synonyme für viele boolesche und Vergleichsoperatoren. So sind »on« und »off« identisch mit »true« und »false«. Verkettete Vergleiche testen schnell, ob eine Variable in einem bestimmten Bereich liegt: »imbild = 0 > Punkt.x > 640«. Die Skriptsprache bietet zudem ein an Ruby angelehntes »switch-case«-Konstrukt.

Daneben erweitert Coffeescript »for«-Schleifen um Comprehensions, mit denen der Code die Elemente eines Array besonders elegant durchläuft. Sie sind zugleich Ausdrücke und lassen sich zuweisen und zurückliefern:

fahre = (a) -> alert(a)
fahre auto for auto in ['bmw', 'vw',  'skoda'] when auto isnt 'skoda'

Der Compiler versucht zudem, Anweisungen in Ausdrücke umzuwandeln. Funktionen liefern etwa immer ihren letzten Wert zurück, daher dürfen Entwickler in vielen Fällen das »return« weglassen. Auch eine »while«-Schleife ist ein Ausdruck, der ein Array mit dem Ergebnis eines Durchlaufs durch die Schleife enthält. Im Beispiel enthält »countdown« ein Array mit den Zahlen 9 bis 0:

zahl = 10
countdown = while zahl > 0
        zahl = zahl - 1

Coffeescript 1.12.4 führt ein eigenes einfaches Klassenkonzept ein, für das Listing 2 ein Beispiel zeigt. Dabei ist »@« die Kurzform von »this.«, über »super« ruft der Entwickler die gerade laufende Funktion in der Oberklasse auf. Das kommende Coffeescript 2.0.0 kompiliert die Klassen in ihre Pendants aus ECMA-Script 2015.

Listing 2

Eine Klassendefinition in Coffeescript

01 class Punkt
02         constructor: (@x,  @y) ->
03
04         zeichne: () -> alert(@x + "," + @y);
05
06 class Rechteck extends Punkt
07         constructor: (x, y, @b,  @h) ->
08                 super x,y
09         zeichne: () -> alert(@x + ";" + @y + ";" + @b + ";" + @h);
10
11
12 r = new Rechteck 1,2,3,4
13 r.zeichne();

Bereits Coffeescript 1.12.4 unterstützt die Generator-Funktionen und die so genannten Tagged Template Literals aus ECMA-Script 2015. Template Literals bauen den Inhalt von Variablen in einen Text ein: »gruss = “Hallo #{name}”«.

Das Coffeescript-Paket umfasst neben dem Compiler auch ein einfaches Buildsystem, das ähnlich wie Make oder Rake arbeitet. Kernpunkt ist ein »Cakefile«, in dem der Entwickler verschiedene Aufgaben für den Compiler vorgibt. Diese lassen sich dann über das Tool »cake« aufrufen. Ein Markdown-Dokument mit der Endung ».litcoffee« verfüttert der Entwickler dann an den Coffeescript-Compiler. Dieser interpretiert alle im Dokument eingerückten Blöcke als Coffeescript-Code und ignoriert den Rest.

Dart

2011 stellte Google seine Skriptsprache Dart der Öffentlichkeit vor [3]. Darin geschriebene Programme führt entweder eine Dart-VM genannte virtuelle Maschine aus oder aber ein Compiler wandelt sie in Javascript-Code um. Die Dart-VM und der Compiler gehören zum von Google bereitgestellten Dart-SDK, es steht unter einer BSD-Lizenz und umfasst noch weitere nützliche Tools. So analysiert etwa der »dartanalyzer« die ihm zugeführten Dart-Skripte und weist auf potenzielle Fehler hin. Zusätzlich existiert mit Dartium ein Chromium-Browser mit integrierter Dart-VM [8].

Über Plugins erlernen IDEs und Editoren wie Atom [9] und Emacs [10] die Skriptsprache, Webstorm [11] unterstützt Dart sogar von Haus aus. Wer das SDK nicht installieren möchte, geht die ersten Schritte im Online-Editor Dartpad [12] aus Abbildung 2. Laut Google eignet sich Dart besonders zum Programmieren größerer Anwendungen. ECMA hat Dart mittlerweile standardisiert [13].

Abbildung 2: Dart-Skripte lassen sich unkompliziert im Dartpad ausprobieren. Rechts unten zeigt es zum Beispiel die Hinweise des Compilers an.

Abbildung 2: Dart-Skripte lassen sich unkompliziert im Dartpad ausprobieren. Rechts unten zeigt es zum Beispiel die Hinweise des Compilers an.

Jedes Dart-Programm besitzt zwingend eine Funktion »main()« als Einsprungspunkt. Ein Beispiel für ein einfaches Skript zeigt Abbildung 2 auf der linken Seite: Es definiert zunächst eine Funktion »sagHallo()«, die das Hauptprogramm in »main()« aufruft. Gibt der Entwickler wie in der Abbildung den Typ einer Variablen an, führt der später eine Typprüfung durch. Neben Fließkommazahlen (»double«) gibt es noch Ganzzahlen (»int«). Beide sind wiederum Subtypen von »num«, das die einfachen Operationen »+«, »-«, »*« und »/« erlaubt.

Des Weiteren kennt Dart Zeichenketten (»String«) und boolesche Werte (»bool«). Ein langer String lässt sich über mehrere Zeilen verteilen. Strings nutzen standardmäßig die UTF-16-Kodierung. Soll eine Variable beliebige Werte aufnehmen, deklariert sie der Entwickler wie in Javascript mit »var«. Variableninhalte fügt Dart über die »$«-Notation in eine Zeichenkette ein. Mit »${exp}« baut der Codeschreiber auch den kompletten Ausdruck »exp« in den Text ein.

Für kurze Funktionen wie »sagHallo()« gibt es zudem die Kurzschreibweise:

String sagHallo(String name) => 'Hallo  $name.';

Mit dem Aufzählungstyp »enum« lässt sich ein eigener Datentyp erstellen. Im folgenden Beispiel nehmen die Variablen vom Typ »Farbe« die drei Werte »Farbe.Rot«, »Farbe.Gelb« oder »Farbe.Blau« an:

enum Farbe { Rot, Gelb, Blau }

Dart kennt nur den Vergleichsoperator »==«. Für Bedingungen gibt es die Kurzschreibweise »var ergebnis = bedingung ? expr1 : expr2« oder noch kürzer: »var ergebnis = expr1 ?? expr2«. Besitzt »expr1« im letzten Fall nicht den Wert »null«, liefert Dart diesen Wert zurück, sonst das Ergebnis von »expr2«.

Sofern Dart an einer Stelle einen booleschen Wert erwartet, gilt nur »true« als wahr. Anders als in Javascript gibt daher folgender Code nichts aus:

var huber = 'Herr Huber';
if (huber) print('Tach!');

Ergänzend zu Arrays – von Dart als Listen bezeichnet – gibt es noch so genannte Maps, die Schlüssel-Wert-Paare speichern. Neben den aus Javascript bekannten For-Schleifen iteriert der Programmierer zusätzlich mit »foreach()« über die Elemente eines Objekts. Listen und andere iterierbare Objekte durchläuft »for … in«. Mit »break« verlässt der Entwickler »while«-Schleifen, »continue« startet sofort den nächsten Schleifendurchlauf. Beide Schlüsselwörter kommen auch in Switch-Case-Konstrukten vor.

Auf Klassenfahrt

In Dart ist alles ein Objekt. Variablen nehmen Funktionen auf, wodurch Letztere auch als Argumente für andere Funktionen herhalten. Der Entwickler darf Parameter als optional kennzeichnen und wie in Coffeescript mit Standardwerten vorbelegen. Dart unterstützt anonyme beziehungsweise Lambda-Funktionen sowie Closures. Typen lassen sich zur Laufzeit mit den Schlüsselwörtern »as«, »is« und »is!« (für »ist nicht«) testen: »(p as Person).vorname = ‘Klaus’;«

Listing 3

Beispiel für eine Klassendefinition in Dart

01 class Punkt {
02         num x;
03         num y = 12;
04         num _deckkraft = 1;
05         Punkt(this.x, this.y);
06         Punkt.aufXAchse(num x) : this(x, 0);
07         Punkt operator +(Punkt p) {
08                 return new Punkt(x + p.x, y + p.y);
09         }
10 }
11
12 class Quadrat extends Punkt {
13         num breite;
14         Quadrat(x, y, b) : super(x,y) {this.breite=b;}
15         num get rechts => this.x + this.breite;
16         set rechts(num w)  => this.breite = w - this.x;
17 }
18
19 main() {
20         var a = new Punkt(1,2);
21         var b = new Punkt(3,4);
22         var c = a + b;
23
24         var d = new Quadrat(1,2,3);
25         d.rechts=20;
26 }

Ein Beispiel für eine Klassendefinition zeigt Listing 3. Der Konstruktor trägt in Dart den gleichen Namen wie die Klasse. Im Beispiel nutzt er zudem eine Kurzschreibweise, die »x« und »y« direkt die übergebenen Werte zuweist. Alternativ lassen sich die Variablen auflisten, wobei die Zuweisung noch vor Ausführung des Konstruktor-Rumpfs erfolgt:

Punkt(num a, num b) : x=a, y=b { ... }

Bei Bedarf benennen Entwickler Konstruktoren und heben so ihren Zweck hervor, wie es Listing 3 in »Punkt.aufXAchse()« tut. Das angehängte »: this(x,0)« ruft den namenlosen Konstruktor auf. Anders als bei der Konkurrenz kann der Entwickler einige Operatoren überladen, etwa das »+«. Im Listing addiert er so später zwei Punkte.

Die Vererbung erfolgt mit »extends«, »super« greift auf die Oberklasse zu. Alle Methoden und Properties sind öffentlich. Beginnt ihr Name wie im Listing bei »_deckkraft« mit einem Unterstrich, sind sie automatisch nur noch innerhalb der Klasse sichtbar (»private«).

Für »get«- und »set«-Methoden bietet Dart eine abgekürzte Schreibweise, die in Listing 3 die Klasse »Quadrat« demonstriert. Daneben lassen sich konstante Objekte erstellen, die sich im Programmverlauf nie verändern. Mit »static« gekennzeichnete Properties und Methoden teilen sich alle Objekte der Klasse.

Mit so genannten Factory-Konstruktoren stellt ein Entwickler sicher, dass von der Klasse nur ein einziges oder ganz bestimmtes Objekt existiert. Über Mixins vereint er mehrere Klassen in einer:

class Auto extends Motor with Karosserie { //... }

Tauscht man in Listing 3»extends« gegen »implements«, würde »Quadrat« von »Punkt« nur die Schnittstelle, nicht aber die Implementierungen erben. Von als »abstract« markierten Klassen leitet Dart lediglich Klassen ab, kann aber keine Objekte anlegen.

Den Zugriff auf nicht-existierende Methoden fängt der Entwickler in Dart bei Bedarf ab. Dazu muss er in der entsprechenden Klasse nur die Methode »noSuchMethod(Invocation mirror)« implementieren. Bietet die Klasse eine Methode namens »call()« an, lässt sich ihr Objekt wie eine Funktion aufrufen.

Abgeleitete Klassen können die Methoden der Oberklasse mit einer eigenen Variante überschreiben. Dazu muss der Entwickler sie mit »@override« markieren:

class Rechteck extends Punkt {
        @override
        void zeichne() { ... }
}

Neben »@override« kennt Dart derzeit noch weitere solcher Annotations – hier Metadata genannt. Eine mit »@depricated« versehene Methode sollten Dart-Entwickler nicht mehr nutzen. Abschließend lassen sich Objekte auch schnell aus Json-Daten herstellen:

var punkt = JSON.decode('{"x":1, "y":2}');

Dart unterstützt auch Generics. Dabei steht beim Deklarieren einer Klasse oder einer Funktion noch nicht fest, welche Werte eines ihrer Objekte später einmal verarbeitet. Als Typ nutzt der Entwickler einen Platzhalter, in folgendem Beispiel ist dies das »T«. Über »extends« in den spitzen Klammern nagelt er zudem die Typen auf bestimmte (Unter-)Klassen fest. Den eigentlichen Typ definiert er erst beim Erzeugen des Objekts:

class Auto<T extends Daimler> {
        T fahre(T modell) { ... };
}
var mb = new Auto<Mercedes>();

Beim Behandeln von Ausnahmen orientiert sich Dart an Java: Entwickler werfen per »throw« jedes Objekt und fangen es per »catch«. Dart stellt vorgefertigte Klassen zur Fehlerbehandlung bereit, Entwickler leiten aber auch eigene Klassen von »Error« und »Exception« ab.

Dart unterstützt asynchrones Programmieren: Eine mit »async« markierte Funktion kehrt umgehend zurück, noch bevor Dart die in ihrem Rumpf enthaltenen Anweisungen ausführt. Analog wartet ein mit »await« markierter Funktionsaufruf, bis die asynchrone Funktion ihre Arbeit beendet hat. Mit dem Future-API bietet Dart zudem ein Konzept, das den Promises aus ECMA-Script 2015 ähnelt.

Dart-Code lagern Programmierer auf Wunsch in so genannte Libraries aus. Jede dieser Bibliotheken bildet gleichzeitig einen eigenen Namensraum: Alle in einer Library mit einem Unterstrich beginnenden Funktionen und Variablen sind nur innerhalb der entsprechenden Library sicht- und nutzbar. Die »import«-Anweisung bindet Libraries in das eigene Dart-Skript ein, wobei die Libraries auch auf anderen Servern liegen dürfen.

Dart unterstützt sogar das so genannte Lazy Loading, bei dem das Dart-Programm die Library erst dann lädt, wenn es sie wirklich benötigt. Existierende Javascript-Bibliotheken lassen sich nur über ein API ansprechen.

Elm

Evan Czaplicki hat Elm [4] 2012 geschaffen, mittlerweile liegt die Entwicklung bei der Elm Software Foundation. Im Gegensatz zur Konkurrenz handelt es sich um eine funktionale Programmiersprache. Den in Elm geschriebenen Code übersetzt zwar ein Compiler in Javascript-Code, vorhandene Javascript-Bibliotheken lassen sich aber nur über eine spezielle Schnittstelle nutzen.

Der Compiler untersteht einer BSD-Lizenz und ist in Haskell geschrieben. Er weist nicht nur auf Fehler hin, sondern liefert auch gleich Lösungsvorschläge (Friendly Error Messages). Alternativ zum Compiler nutzen Entwickler eine spezielle Console (die Repl), die eingetippten Elm-Code direkt ausführt. Für mehrere gängige Editoren, etwa Atom, Intellij Idea [14] und Emacs, stehen Plugins bereit. Unverbindlich ausprobieren dürfen Interessierte Elm in einem Online-Editor [15].

Elm selbst kennt nur wenige Konstrukte und Schlüsselwörter. Bei der Definition einer Variablen darf der Entwickler den Typ mit angeben. Elm unterscheidet zwischen booleschen Werten, Ganzzahlen, Fließkommazahlen, einzelnen Zeichen und Zeichenketten:

zahl: Int
zahl = 42

Der Compiler leitet automatisch die Typen der Variablen ab und warnt vor Problemen. Per »++« schweißen Elm-Entwickler Zeichenketten zusammen. Die Programmiersprache unterscheidet die Ganzzahldivision »/« und die Division von Fließkommazahlen »//«. Kommentare beginnen mit »–«. Neben den genannten Datentypen gibt es noch Listen, die Javascript-Arrays ähneln:

farben = [ "Rot", "Blau", "Gelb" ]

Listen lassen sich mit Funktionen aus einer mitgelieferten Bibliothek manipulieren. Beispielsweise sortiert »List.sort farben« die Liste »farben«. Ergänzend existieren Tupel wie »(“Klaus”, 32)«, die eine feste Anzahl von beliebigen Werten enthalten. So genannte Records wiederum nehmen mehrere Variablen auf, wobei der Punktoperator zum Wert einer Variablen führt:

punkt = { x = 1, y = 2 }
punkt.x

Alternativ weist der Entwickler Elm mit ».x punkt« an, die Variable »x« im Record »punkt« zu holen. Er ändert die Werte in einem Record über »{ punkt | x = 3 }«. Alle Manipulationen an Records erfolgen nicht-destruktiv. Elm ändert folglich nicht im Record »punkt« den Wert von »x«, sondern generiert einen komplett neuen Record. Der Compiler stellt dabei eine effiziente Speichernutzung sicher. In Funktionen nutzt der Entwickler die Elemente von Records direkt:

dist {x,y} = sqrt (x^2 + y^2)

Union Types entsprechen schließlich noch »enum« aus Dart. Im folgenden Beispiel entsteht der neue Datentyp »Farbe«, wobei Variablen wahlweise den Wert »Rot«, »Gelb« oder »Blau« annehmen:

type Farbe = Rot | Gelb | Blau

Funktionen definieren Entwickler ohne die üblichen Klammern, die auch beim Aufruf fehlen. Mehrere Parameter trennt der Entwickler nur durch Leerzeichen:

quadrat n = n * n

Wer Funktionen nicht verschachtelt aufrufen möchte, kann sie auch mit dem Pipe-Operator »|>« hintereinander aufschreiben. Elm unterstützt anonyme beziehungsweise Lambda-Funktionen wie etwa: »\n -> n / 2«. Hinter »\« steht der Name des Parameters, hinter dem Pfeil folgt der Funktionsrumpf. Bedingungen sind ebenfalls kurz und knapp:

istPositiv zahl = if zahl > 0 then "Positiv" else "Nicht positiv"

Alternativ gibt es ein »case … of« (wie in Abbildung 3). Die Sichtbarkeit von Variablen beschränkt der Entwickler mit »let … in« auf einen ganz bestimmten Ausdruck. Damit ist der Sprachumfang im Wesentlichen beschrieben. Den Bau einer kompletten Webanwendung sowie den Zugriff auf den HTML-Code ermöglichen mitgelieferte Funktionen. Diese nutzen durchweg ein Model-View-Controller-Konzept, das die Entwickler als Elm Architecture bezeichnen (Abbildung 3).

Abbildung 3: Elm bietet im Online-Editor einige vorgefertigte Beispiele. Das gew&auml;hlte namens <code>Button</code> erzeugt beispielsweise die zwei Schaltfl&auml;chen auf der rechten Seite, &uuml;ber die der Benutzer die Zahl ver&auml;ndern kann.

Abbildung 3: Elm bietet im Online-Editor einige vorgefertigte Beispiele. Das gewählte namens »Button« erzeugt beispielsweise die zwei Schaltflächen auf der rechten Seite, über die der Benutzer die Zahl verändern kann.

Typescript

Auch Microsoft werkelt schon seit mehreren Jahren emsig an einer Javascript-Alternative namens Typescript [5]. Die Sprache ergänzt wie Coffeescript die aktuelle ECMA-Script-Version um einige eigene Konstrukte. Javascript-Bibliotheken wie Jquery bleiben somit in Typescript weiter verwendbar. Mittlerweile fließen sogar regelmäßig Elemente aus Typescript zurück in den offiziellen ECMA-Script-Standard. Microsoft bietet für Typescript einen Compiler an, der Typescript-Programme in ECMA-Script übersetzt [16]. Testen können Entwickler die Typescript-Programme wie bei der Konkurrenz online im Playground-Editor (Abbildung 4, [17]).

Abbildung 4: Im Playground-Editor stehen fertige Beispiele f&uuml;r Typescript parat. Hier demonstriert <code>Using Classes</code> den Umgang mit Klassen.

Abbildung 4: Im Playground-Editor stehen fertige Beispiele für Typescript parat. Hier demonstriert »Using Classes« den Umgang mit Klassen.

Wie in Dart dürfen Entwickler den Typ einer Variablen festlegen, der Compiler führt dann eine statische Typprüfung durch. Im folgenden Beispiel definiert die zweite Zeile eine Funktion, der Rückgabetyp steht hinter dem Namen:

var name: string = "Tim";
function ausgabe: void (a: string) { console.log(a); }

Das »void« weist den Compiler darauf hin, dass die Funktion nichts zurückliefert. Ergänzend gibt es noch den speziellen Rückgabetyp »never«. Mit ihm gekennzeichnete Funktionen beenden sich nicht, wie es etwa bei Exceptions der Fall ist. Neben Strings kennt Typescript noch boolsche Werte (»boolean«) und Fließkommazahlen (»number«).

Ist der Typ einer Variablen unbekannt, weist ihr der Entwickler explizit den Typ »any« zu. Dadurch schaltet der Compiler die Typprüfung ab. Umgekehrt kann er den Typ mit gleich zwei Notationen explizit prüfen lassen:

var laenge: any = (<string>vorname).length;
var laenge: any = (vorname as string).length;

In Funktionen darf der Entwickler für einen Parameter mehrere alternative Typen erlauben. Im folgenden Beispiel übergibt er entweder eine Zahl oder einen String:

function foo(wert: string | number) {...}

Zudem kann er die exakten Werte vorgeben, die ein String enthalten darf (String Literal Types). Das ist nützlich, um bestimmte Eingaben in Webanwendungen zu erzwingen. Wie Dart kennt Typescript den Aufzählungstyp »enum«:

enum Farbe {Rot, Gelb, Blau};

Eine Alternative sind Tupel. Im folgenden Beispiel speichert »arbeiter« die beiden Werte »Klaus« und »34«. Der Zugriff auf dieses Wertepaar erfolgt wie bei einem Array über den Index:

var arbeiter: [string, number];
arbeiter = ["Klaus", 34];
arbeiter[1] = 35;

Klassen definiert der Entwickler wie in ECMA-Script 2015. Auf Wunsch übersetzt sie der Typescript-Compiler in älteres ECMA-Script. Steht vor einer Property oder einer Methode das Schlüsselwort »private«, dürfen nur noch die Methoden der Klasse darauf zugreifen. Im Fall von »protected« erhalten hingegen auch alle abgeleiteten Klassen Zugriff.

Über das Schlüsselwort »static« legt Typescript Properties an, die sich alle Objekte einer Klasse teilen. Typescript bietet zudem eine Notation für Getter- und Setter-Methoden:

class Auto {
        protected _modell: string;
        get modell(): string { return this. _modell; }}

Typescript ergänzt das Klassenkonzept um Interfaces, die lediglich den Aufbau beziehungsweise die Schnittstelle eines Objekts beschreiben (wie in Listing 4). Eine mit einem Fragezeichen gekennzeichnete Property ist optional. Via »extends« erweitert ein Interface ein anderes Interface oder aber eine Klasse, wobei im letzten Fall das Interface nur die Methoden und Properties aus der Klasse übernimmt.

Listing 4

Beispiel für Interfaces in Typescript

01 interface Person {
02         vorname: string;
03         nachname?: string;
04 }
05
06 interface Student extends Person {
07         matrikelnr: number;
08 }
09
10 function sagHallo(person : Person) {
11         console.log("Hallo " + person.vorname);
12 }
13
14 class Mitarbeiter {
15         name: string;
16         constructor(public vorname, public nachname) {
17                 this.name = vorname + " " + nachname;
18         }
19 }
20
21 var klaus = new Mitarbeiter("Klaus", "Meier");
22 sagHallo(klaus);

In Typescript sind zwei Typen miteinander kompatibel, wenn ihre interne Struktur übereinstimmt. Entwickler müssen daher nicht wie in anderen Sprachen explizit das Interface implementieren. Es genügt, wenn ein Objekt die im Interface beschriebene Schnittstelle anbietet. Ein Beispiel zeigt Listing 4, in dem »sagHallo()« auch einen »Mitarbeiter« korrekt verarbeitet. Ein Interface beschreibt neben Klassen auch Funktionen. Ergänzend zu den Interfaces gibt es noch abstrakte Klassen, die es lediglich erlauben, weitere Klassen abzuleiten.

Bei mit »const« erstellten Objekten darf der Entwickler außerdem die Properties des Objekts ändern. Das unterbindet erst das Schlüsselwort »readonly« vor der entsprechenden Property. Die so genannten Indexable Types verwandeln ein Interface in ein Array oder Dictionary.

Im folgenden Beispiel bestehen die »Mitarbeiter« aus mehreren Namen:

interface Mitarbeiter {
        [index: number]: string;
}

Der Zugriff erfolgt wie bei einem Array. Im Beispiel ist der Index eine Zahl (»number«), die gespeicherten Werte sind Texte (»string«). Tauscht der Programmierer »number« gegen »string«, greift er auf die gespeicherten Werte über Begriffe zu, etwa »plz[“Dortmund”]«.

Typscript fasst in bestimmten Fällen mehrere Deklarationen zu einer zusammen (Declaration Merging). Im folgenden Beispiel verbindet der Compiler beispielsweise automatisch die beiden Interface-Deklarationen:

interface Punkt { x: number; }
interface Punkt { y: number; }
let p: Punkt = {x: 1, y: 2};

Auch Typescript bietet die so genannten Generics an, deren Notation der von Java beziehungsweise Dart ähnelt. Mixins setzen mehrere Klassen zu einer neuen zusammen. Ergänzend gibt es noch die Intersection Types, die das Kombinieren von Typen erlauben. Zum Beispiel ist »Person & Mitarbeiter & Student« sowohl eine Person, als auch ein Mitarbeiter und ein Student.

Typescript unterstützt ECMA-Script-Module. Der Compiler erzeugt zudem passenden Code für die Modul-Lösungen von Node.js (Common JS), Require JS (AMD), Isomorphic (UMD) und System JS. Typescript kapselt auf Wunsch Variablen, Klassen und andere Elemente in einem eigenen Namespace. Das verringert das Risiko, dass eine importierte Funktion so heißt wie eine eigene.

Experimentierfreudige dürfen in der aktuellen Version bereits Decorators nutzen. Sie verleihen einer Klasse, Property oder Methode zusätzliche Eigenschaften. Das Beispiel weist der Klasse »Foo« den Decorator »@sealed« zu. Was dieser wann bewirkt, bestimmt der Entwickler in einer eigenen Funktion:

@sealed
class Foo { ... }

Der Typescript-Compiler unterstützt die JSX-Spezifikation [18] und weist auf typische Javascript-Fehler hin. Plugins integrieren Typescript für mehrere Buildtools wie etwa Maven. Auch für Atom, Eclipse und viele weitere Editoren oder IDEs existieren Plugins. Der Compiler selbst ist in Typescript geschrieben und benötigt zur Ausführung Node.js.

Die von Microsoft entwickelten Tools nebst Compiler stehen unter der Apache-2.0-Lizenz, die Spezifikation der Sprache unter Version 1.0 der offenen Lizenz Open Web Foundation Final Specification Agreement (OWF 1.0, [19]).

Fazit

Die Wahl zwischen Coffeescript, Dart, Elm und Typescript hängt vom jeweiligen Projekt, den Anforderungen und nicht zuletzt vom eigenen Geschmack ab. Coffeescript hat gegenüber ECMA-Script etwas den Anschluss verloren und bietet zudem keine Typprüfung. Der Vorteil dieser Skriptsprache reduziert sich also wesentlich auf die von Python abgeschaute Codeformatierung mit Tabulatoren und die Möglichkeit, Markdown mit Coffeescript zu mischen.

Dart wiederum lässt sich in einer speziellen VM ausführen. Die Sprache bietet moderne objektorientierte Konzepte wie Mixins, die sich aber mehr an Java und C als an Javascript orientieren.

Elm richtet sich vor allem an Liebhaber funktionaler Programmierung. Diese entwickeln in gewohnten Denkmustern schnell Webanwendungen. Durch die automatische Typableitung treten zudem Laufzeitfehler in Elm selten auf.

Typescript dient mittlerweile als Spielwiese für zukünftige ECMA-Script-Versionen. Da es auf Javascript basiert, fällt die Umstellung leicht. Die Typisierung sorgt hier auch für weniger Laufzeitfehler. Bei Dart und Typescript darf der Entwickler typisierte und typenlose Variablen allerdings munter mischen. Trotz der Compiler-Unterstützung bleibt so eine kleine Fehlerquelle.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 7 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben