Aus Linux-Magazin 01/2013

Coffeescript statt Javascript

© manun, photocase.com

Zahlreiche Sprachen versuchen das in die Jahre gekommene Javascript zu verbessern oder sogar abzulösen. Dazu gehört das recht frische Coffeescript. Es nimmt einige behutsame, aber effiziente Vereinfachungen vor, lässt sich in Javascript-Code übersetzen und ist damit erstaunlich erfolgreich.

An den Weihnachtsfeiertagen des Jahres 2009 hatte Jeremy Ashkenas alle Hände voll zu tun. Mit großem Eifer veröffentlichte er hintereinander mehrere Versionen einer neuen Programmiersprache. Sie sollte einfacher zu lesen sein als Javascript und sich doch in Javascript-Code übersetzen lassen. Bereits zwei Jahre später integrierte das Ruby-on-Rails-Projekt die zwischenzeitlich Coffeescript [1] getaufte Sprache. Im September 2012 verkündete schließlich die Firma Dropbox, sie habe den kompletten Javascript-Code ihres Browser-Clients nach Coffeescript portiert [2]. Die vom Cloudspeicherdienst vorgelegten Zahlen beeindrucken: Der Code des Dropbox-Clients schrumpfte um etwa 21 Prozent.

Sanft verbessert

Coffeescript ist allerdings keine komplett neu entwickelte Sprache. Jeremy Ashkenas hat sich einfach das gute alte Javascript genommen, darin ein paar komplexere Konstrukte gegen besser lesbare Abkürzungen ausgetauscht und das Ganze noch mit etwas Haskell und Ruby gewürzt. So darf der Programmierer beispielsweise anstelle von

alert("Hallo Welt!");

einfach kurz und knapp

alert "Hallo Welt!"

schreiben. Damit ist Coffeescript-Code einerseits wesentlich kompakter und somit leichter zu warten, andererseits können Programmierer bestehenden Javascript-Code einschließlich existierender Frameworks direkt in Coffeescript weiterverwenden. Wie im Fall von »alert« sind einige der kürzeren Schreibweisen optional, die meisten jedoch Pflicht.

Da Coffeescript-Code zurzeit nicht direkt in Browsern läuft, stellen die Spracherfinder einen Compiler bereit, der Coffeecript-Code in normales Javascript überführt. Der dabei erzeugte Code soll für Menschen gut lesbar sein, beim Test mit Javascript Lint [3] nicht durch Syntaxfehler oder Warnungen auffallen, in jeder Javascript-Laufzeitumgebung funktionieren und zumindest genau so schnell sein wie äquivalenter, per Hand geschriebener Javascript-Code.

Das alles versprechen zumindest Jeremy Ashkenas und seine mittlerweile zahlreichen Helfer. Als Bonus liegt dem Compiler noch ein kleiner Interpreter bei, der Coffeescript-Programme direkt auf der Kommandozeile ausführt. Wie man die Werkzeuge unter Linux zum Laufen bringt, verrät der Kasten “Installation”.

Installation

Der Coffeescript-Compiler ist selbst in Coffeescript geschrieben. Das hat den Vorteil, dass man ihn sogar in einem Browser ausführen kann. Der Kommandozeileninterpreter nutzt jedoch das Javascript-Framework Node.js [4]. Zur Installation sind daher unbedingt Node.js sowie der Node Package Manager (»npm« ) erforderlich. Einige Distributionen, etwa Ubuntu, halten beides vor. Sobald die Pakete eingespielt sind, genügt zur Installation der Coffeescript-Werkzeuge das Kommando:

sudo npm install -g coffee-script

Den Quellcode der Werkzeuge gibt es zusätzlich auf der Coffeescript-Homepage [1].

Um auf der Kommandozeile Coffeescript-Code auszuführen, legt ihn der Programmierer in einer Datei mit der Endung ».coffee« ab und lässt den Interpreter darauf los (Abbildung 1):

Abbildung 1: Zunächst führt der Interpreter das Skript »hallo.coffee« aus, dann übersetzt es der Compiler in Javascript-Code.

Abbildung 1: Zunächst führt der Interpreter das Skript »hallo.coffee« aus, dann übersetzt es der Compiler in Javascript-Code.

coffee hallo.coffee

An dieser Stelle gibt es allerdings zwei kleine Stolpersteine: Zunächst startet der Interpreter das Programm »node« , das unter einigen Distributionen jedoch »nodejs« heißt. Wer eine entsprechende Fehlermeldung erhält, muss entweder per Hand einen Symlink einrichten oder noch einmal einen Blick in den Paketmanager werfen: Ubuntu bietet für diese Zwecke eigens das Paket »nodejs-legacy« an.

Des Weiteren stehen auf der Kommandozeile nicht alle Javascript-Funktionen bereit. Das gilt insbesondere für solche Exemplare, die auf ein Fenster angewiesen sind, etwa »alert()« . Um dem Interpreter ein “Hallo Welt” zu entlocken, muss der Programmierer ihn daher mit dem Coffeescript-Code

conosole.log "Hallo Welt"

füttern. Den Compiler weckt er schließlich noch über den Parameter »-c« :

coffee -c hallo.coffee

Der aus »hallo.coffee« erzeugte Javascript-Code landet dabei in der Datei »hallo.js« .

Im Browser

Die ersten Gehversuche mit Coffeescript kann der Neugierige aber auch direkt im Web machen. Dazu klickt er auf der Coffeescript-Homepage auf »Try Coffeescript« und hinterlegt seinen Code im blauen Fenster, der so genannten Coffeescript-Konsole. Diese übersetzt schon während des Tippens im Hintergrund den Code und zeigt das Resultat auf der rechten Seite an. Ein Klick auf »Run« führt die Eigenkreation schließlich aus.

Wie das einfache Hallo-Welt-Beispiel weiter oben zeigt, darf der Coffeescript-Coder das Semikolon am Zeilenende weglassen, der Zeilenumbruch dient als Ersatz. Auch die geschweiften Klammern haben ausgedient: Wie in Python klären Einrückungen, zu welchem Block eine Zeile gehört. Wie übersichtlich man damit ein Objekt definiert, zeigt Listing 1, den daraus erzeugten Javascript-Code Listing 2. Variablen darf der Programmierer direkt benutzen, das passende »var« ergänzt Coffeescript beim Kompilieren. Wie in Shellskripten leitet das Hash-Zeichen »#« einen Kommentar ein.

Listing 2

Javascript-Code zu Listing 1

01 // Javascript
02 var autos;
03
04 autos = {
05   bmw: {
06     farbe: "Blau",
07     baujahr: 1990
08   },
09   vw: {
10     farbe: "Rot",
11     baujahr: 1988
12   }
13 };

Listing 1

Definition mit Einrückungen

01 # Coffeescript
02 autos =
03     bmw:
04         farbe: "Blau"
05         baujahr:  1990
06     vw:
07         farbe: "Rot"
08         baujahr:  1988

Zahlenfolgen darf man prägnant als Bereich schreiben. So steht etwa »[1..10]« für die Zahlen von 1 bis 10. Diese Notation lässt sich in Coffeescript dazu verwenden, elegant Teile aus einem Array auszuschneiden. Ein Beispiel dafür zeigt Listing 3. Zwei Punkte zwischen den Positionsangaben – also »[1..3]« – übernehmen auch die Position 1 und die Position 3 mit ins neue Array, bei drei Punkten – »[1…3]« – wandert das Element an Position 3 (in Listing 3 der »BMW« ) nicht mit in das neue Array.

Listing 3

Arrays manipulieren

01 autos = ["VW", "Mercedes", "Ferrari", "BMW"]
02
03 rennwagen = autos[1..3]
04 formel1 = autos[1...3]
05
06 alert rennwagen
07 alert formel1

Funktional

In Funktionsaufrufen darf man die Klammern weglassen. Den Ausdruck

console.log fakultaet x

übersetzt Coffeescript in »console.log(fakultaet(x))« . Für die Deklaration von Funktionen gibt es zudem eine vereinfachte Schreibweise. So fällt das Schlüsselwort »function« weg und der Rückgabewert steht hinter einem Pfeil »->« . Ebenso darf der Programmierer Argumente mit Standardwerten belegen. Coffeescript macht aus der Zeile

quadrieren = (x = 2) -> x * x

das Javascript aus Listing 4. Coffeescript-Funktionen dürfen beliebig viele Argumente übergeben bekommen. In Listing 5 muss man die Funktion »a« mindestens mit zwei Argumenten aufrufen, etwa »a 1,2« . Diese beiden Zahlen landen in den Variablen »x« und »y« . Alle weiteren übergebenen Werte wandern in das Array »rest« . Die drei Punkte (Splats) zeigen an, dass der Aufrufer hier beliebig viele Werte übergeben darf. Listing 5 liefert somit als Ausgabe »3,4,5,6« .

Listing 5

Splats

01 a = (x, y, rest...) ->
02     alert rest
03
04 a 1,2,3,4,5,6

Listing 4

Übersetzte Funktion quadrieren

01 var quadrieren;
02
03 quadrieren = function(x) {
04   if (x == null) {
05     x = 2;
06   }
07   return x * x;
08 };

Möchte der Programmierer die Inhalte von zwei Variablen »a« und »b« vertauschen, benötigt er normalerweise eine dritte als Zwischenspeicher. Coffeescript kennt jedoch eine kürzere Notation:

[a, b] = [b, a]

Sie weist die Variableninhalte auf der rechten Seite den Variablen auf der linken zu. Auf der rechten Seite darf sogar ein beliebiges Array stehen. Dieses Konzept namens Destructuring Assignment soll übrigens auch in einer der nächsten Versionen des Standards Ecmascript Einzug halten [5].

Mit dieser Notation lassen sich außerdem auch Funktionen schreiben, die mehrere Werte zurückgeben. Listing 6 zeigt den Trick: Dort gibt die Funktion »adresse« einfach ein Array mit den entsprechenden Werten zurück, die Coffeescript in der zweiten Zeile den Variablen »name« , »strasse« und »ort« zuweist. Wem jetzt der Kopf schwirrt, der sollte einen Blick auf den resultierenden Javascript-Code in Abbildung 2 werfen.

Listing 6

Destructuring Assignment

01 adresse = () -> ["hans", "musterstr", "musterdorf"]
02 [name, strasse, ort] = adresse()
03 alert name
Abbildung 2: Die Coffeescript-Konsole im Browser: Die Elemente des von »adresse()« zurückgelieferten Array landen jeweils in den Variablen »name«, »straße« und »ort«.

Abbildung 2: Die Coffeescript-Konsole im Browser: Die Elemente des von »adresse()« zurückgelieferten Array landen jeweils in den Variablen »name«, »straße« und »ort«.

Packstation

Das Schlüsselwort »do« führt eine Funktion sofort aus. Ein einfaches Beispiel gibt Listing 7, die Javascript-Übersetzung zeigt Listing 8: Die Variable »quadrat« enthält dort nicht etwa eine Funktion, die »x * x« ausrechnet, sondern das Ergebnis »9« . Damit das funktioniert, stülpt »do« der Funktion »x * x« zunächst eine leere Funktion über, leitet dann alle Argumente weiter und startet das entstandene Konstrukt. Auf diese Weise lassen sich übrigens auch Closures basteln. Apropos Verpacken: Um bei der Zusammenarbeit mit vorhandenem Javascript-Code nicht in Probleme zu laufen, verpackt Coffeescript den kompletten Code noch einmal in eine anonyme Funktion »(function(){ … })();« . Das verhindert zwar, dass Variablen versehentlich global werden, wer jedoch Variablen außerhalb von Coffeescript anbietet, muss sie beispielsweise als Eigenschaften dem »window« -Objekt anhängen.

Listing 8

Javascript-Code zu Listing 7

01 var quadrat, x;
02 x = 3;
03
04 quadrat = (function(x) {
05   return x * x;
06 })(x);
07
08 alert(quadrat);

Listing 7

Einsatz von do

01 var quadrat, x;
02 x = 3;
03
04 quadrat = (function(x) {
05   return x * x;
06 })(x);
07
08 alert(quadrat);

Alles unter Kontrolle

Auch bei »if« -Abfragen fallen die geschweiften Klammern weg, den Rumpf trennt in Zweifelsfällen ein »then« :

if [...] then [...] else [...]

Zusätzlich zu den aus Javascript bekannten logischen Operatoren wie »&&« oder »||« bietet Coffeescript die besser lesbaren »and« , »or« , »is« und »not« an. Den Vergleichsoperator »==« ersetzt der Coffescript-Compiler immer in »===« sowie »!=« in »!==« . Außerdem darf der Programmierer bei Einzeilern eine schlanke Postfix-Notation verwenden. Listing 9 zeigt das Javascript zu der folgenden Zeile:

Listing 9

Übersetzte if-Abfrage

01 var ps;
02
03 if (bmw || mercedes) {
04   ps = 230;
05 }
ps = 230 if bmw or mercedes

Vergleichsoperatoren kann der Entwickler wie in Python hintereinanderhängen und damit rasch testen, ob etwa die Temperatur im erträglichen Bereich liegt:

if (15 > temperatur < 30) then [...]

Wie findet der Programmierer heraus, ob eine Variable »x« existiert? Coffeescript bietet dafür den praktischen Fragezeichen-Operator an. Der liefert »true« , wenn die Variable schon existiert und zudem nicht »null« ist:

if x? then x = 1

Dieser Operator hilft auch dabei, in hintereinandergeschalteten Aufrufen »null« -Referenzen “aufzusaugen”:

autonr = garage.oeffnen?().auto?.kennzeichen()

Sollte ein Objekt (wie das »auto« ) oder eine Funktion (wie »garage.oeffnen()« ) nicht existieren, liefert der ganze Ausdruck »undefined« zurück, es entsteht also kein Type-Error.

Fallunterscheidungen lassen sich in Javascript durch das kompakte »switch« ersetzen. In Coffeescript hat sich dieses der Klammern, der Breaks und des Default entledigt, Listing 10 zeigt ein Beispiel. Coffeescript ersetzt die »for« -Schleifen aus Javascript durch ein »for […] in« . Die Zeile

Listing 10

Fallunterscheidung mit switch

01 sprache = "DE"
02 switch sprache
03     when "EN" then alert "Hello!"
04     when "DE", "AT" then alert "Tag!"
05     else alert "Sprache unbekannt"
alert auto for auto in ["bmw", "vw", "opel"]

verwandelt der Compiler in Listing 11. Wie dort zu sehen ist, durchläuft das Programm das Array mit den Autonamen. Dabei steckt es den aktuellen Namen in die Variable »auto« , die es wiederum an die Funktion »alert« übergibt.

Listing 11

Übersetzte for-Schleife

01 var auto, _i, _len, _ref;
02
03 _ref = ['bmw', 'vw', 'opel'];
04 for (_i = 0, _len = _ref.length; _i < _len; _i++) {
05   auto = _ref[_i];
06   alert(auto);
07 }

For-Comprehensions

Ergänzend erlaubt es »for […] in« , so genannte Comprehensions aufzubauen, also Funktionen, die aus einer Liste eine neue generieren. Das folgende Beispiel durchläuft die Zahlen 1 bis 10 in durch die Angabe »by 2« erzwungenen Zweierschritten. Jede der Zahlen 1, 3, 5, 7, 9 quadriert Coffeescript (»x*x« ) und speichert schließlich alle Ergebnisse im Array »quadrate« :

quadrate = (x*x for x in [1..10] by 2)

Das per »alert quadrate« ausgegebene Ergebnis lautet damit »1,9,25,49,81« . Alternativ kann man auch die Eigenschaften von Objekten durchlaufen. Die Zeilen aus Listing 12 führen zur Ausgabe »BMW ist Blau, VW ist Rot« . Das Listing zeigt auch noch eine weitere, von Ruby abgeschaute Notation: »#{farbe}« ersetzt Coffeescript innerhalb des Strings durch den Inhalt der Variablen »farbe« , analog gilt dies für die »marke« . Auf diese Weise lassen sich elegant komplexere Zeichenketten zusammensetzen.

Listing 12

Iteration über Eigenschaften

01 autos = BMW: "Blau", VW: "Rot"
02
03 daten = for marke, farbe of autos
04     " #{marke} ist #{farbe}"
05
06 alert daten

Während die alte »for« -Schleife aus Javascript zumindest in der aktuellen Version von Coffeescript nicht existiert, kennt die Sprache sehr wohl noch die »while« -Schleife. Besteht sie nur aus einer Zeile, lässt sie sich in Postfix-Notation verwenden. In folgendem Beispiel würde »while« so lange »gasgeben()« aufrufen, wie »geschwindigkeit« kleiner als »maximum« ist:

gasgeben() while geschwindigkeit < maximum

In Coffeescript darf man die »while« -Schleife aber auch als Ausdruck verwenden, der ein Array mit den Ergebnissen aus jedem Schleifendurchlauf zurückgibt. Listing 13 zeigt ein Beispiel dafür: Die »while« -Schleife durchläuft einfach die Zahlen von 9 bis 1 und steckt sie in einen String. Alle so erzeugten Strings landen zum Schluss wiederum im Array »countdown« . Bequeme Programmierer dürfen schließlich noch »until« anstelle von »while not« sowie »loop« anstelle von »while true« schreiben.

Listing 13

Erweiterte while-Schleife

01 zaehler = 10
02 countdown = while zaehler -= 1
03   "#{zaehler} Bier sind noch über."
04 alert countdown

Rückgabewerte

Listing 13 liefert einen Wert zurück, obwohl nirgends ein »return« steht. Dies ist möglich, da Coffeescript alle Anweisungen als Ausdruck (Expression) ansieht. Funktionen geben immer automatisch ihren letzten Wert zurück – vorausgesetzt der Coffeescript-Programmierer setzt nicht selbst »return« ein. Darüber hinaus kann er Variablenzuweisungen innerhalb eines Ausdrucks vornehmen, was zu lustigen Zeilen wie der folgenden führt:

sieben = (fuenf = 5) + (zwei = 2)

Einige Anweisungen in Javascript wie »break« oder »continue« lassen sich nicht in einen Ausdruck überführen. Nutzt man diese in Coffeescript, übernimmt sie der Compiler einfach ins fertige Javascript. Wer solche Anweisungen einsetzt, muss folglich wissen, was er tut.

Dank des Schlüsselworts »class« lassen sich auch Klassen in Coffeescript kompakter und vor allem lesbarer aufschreiben. Listing 14 zeigt dafür ein Beispiel. Die Klasse »Punkt« besitzt dort einen Konstruktor, der zwei Argumente übergeben bekommt. Das »@« sorgt im Konstruktor dafür, dass Coffeescript die übergebenen Werte direkt den Attributen namens »x« und »y« zuweist. Der Rumpf des Konstruktors kann somit leer bleiben (nach dem Pfeil folgt eine Leerzeile).

Listing 14

Klassen und Vererbung

01 class Punkt
02   constructor: (@x, @y) ->
03
04   zeichne: ->
05     alert "Position: " + @x + ", " + @y
06
07 class Rechteck extends Punkt
08   constructor: (a, b, @breite) ->
09     super a,b
10
11   zeichne: ->
12     alert "Breite: " + @breite
13     super
14
15 form = new Rechteck 1,2,3
16 form.zeichne()

Allgemein ist die Notation »@x« eine Kurzschreibweise für »this.x« . Davon macht auch die Methode »zeichne« Gebrauch. Die Klasse »Rechteck« ist von »Punkt« abgeleitet, »super« ruft jeweils die entsprechende Methode in der Oberklasse auf – »super« in der Methode »zeichne« ruft folglich die Methode »zeichne« der Klasse »Punkt« auf. Zum Schluss erzeugt Listing 14 noch ein neues »Rechteck« und ruft dessen Methode »zeichne« auf. Konstruktoren tragen den Namen ihrer Klasse, in Listing 14 ist »form.constructor.name« daher »Rechteck« .

Schlaue Callbacks

In Javascript-Code bezeichnet »this« immer das Objekt, an das die gerade ablaufende Funktion gebunden ist. Das führt zu Problemen, wenn Event-Handler und Callback-Funktionen zum Einsatz kommen: Wird die Callback-Funktion aufgerufen, verweist »this« auf genau jenes DOM-Element, welches das Ereignis erzeugt hat. Der Programmierer müsste folglich auf den Einsatz von »this« innerhalb der Callback-Funktion verzichten – oder er nutzt in Coffeescript den Doppelpfeil. Dann zeigt »this« auf das Objekt, in dem die Callback-Funktion definiert ist. Ein Beispiel dafür liefert Listing 15.

Listing 15

Doppelpfeil-Operator

01 Einkauf = (kunde, produkt) ->
02   @kunde = kunde
03   @produkt = produkt
04
05   $('.shopping_cart').bind 'click', (event) =>
06     @kunde.kauft @produkt

Kuchen backen

Zusammen mit Compiler und Interpreter wandert bei der Coffeescript-Installation auch das rudimentäre Buildtool »cake« auf die Festplatte. Analog zu Make wertet es eine Steuerdatei namens »Cakefile« aus, die eine oder mehrere Aufgaben (Tasks) vorgibt. Das »Cakefile« selbst ist wieder ein Coffeescript, der Programmierer darf somit auch entsprechenden Code darin unterbringen.

Listing 16 liefert ein Beispiel für eine solche Datei. Es holt zunächst ein Hilfsobjekt hinzu und definiert dann eine Funktion »build« , die den »coffee« -Compiler alle Coffeescript-Dateien im Unterverzeichnis »src« übersetzen lässt. Abschließend definiert es noch eine Task namens »build« , die einfach nur die Funktion »build()« aufruft.

Listing 16

Cakefile

01 cp = require 'child_process'
02
03 build = (callback) ->
04   coffee = cp.spawn 'coffee', ['-c', 'src']
05
06 task 'build', 'Kompiliere unter src/', ->
07   build()

Um diese Task anzustoßen, ruft der Entwickler »cake build« im Verzeichnis mit dem Cakefile auf, »cake« ohne Parameter zeigt nur die vorhandenen Tasks und deren Beschreibung an. Letztere lautet in Listing 16 »Kompiliere unter src/« .

Fazit

Die Macher versprechen nicht zu viel: Coffeescript-Code ist gegenüber Javascript in weiten Teilen leichter lesbar und erstaunlich kompakt – manchmal allerdings auch zu kompakt. So brauchen viele Programmierer etwas Übung, bis sie auf einen Blick erkennen, wer in der Anweisung »rechne x, (i) -> quadrat i« wen aufruft. Glücklicherweise hilft hier die vorzügliche Coffeescript-Konsole im Web, die den Code schon direkt beim Tippen in Javascript übersetzt.

Der Umstieg von Dropbox auf Coffeescript beweist, dass die neue Sprache alltagstauglich ist. Eine Liste mit weiteren Coffeescript-Anwendungen liefert das Wiki des Projekts [6]. Ausführlichere Informationen zur Sprache und Syntax enthält das kostenlose Buch “Smooth Coffeescript” [7]. Das Werk “The Little Book on Coffeescript” darf man nur in einer älteren, aber immer noch weitestgehend gültigen Auflage im Internet kostenfrei lesen [8]. Die offiziell verfügbare Anleitung auf der Coffeescript-Homepage ist kurz und knackig, richtet sich aber an Javascript-Programmierer – und genau die sollten aufgrund der vielen Vorteile unbedingt reinschnuppern.

Infos

  1. Coffeescript: http://coffeescript.org
  2. Dropbox-Tech-Blog, “Dropbox dives into Coffeescript”: https://tech.dropbox.com/2012/09/dropbox-dives-into-coffeescript/
  3. Javascript Lint: http://www.javascriptlint.com
  4. Node.js: http://nodejs.org
  5. “Destructuring Assignment in ECMAScript”: http://wiki.ecmascript.org/doku.php?id=harmony:destructuring
  6. Projekte und Anwendungen, die Coffeescript nutzen: https://github.com/jashkenas/coffee-script/wiki/In-The-Wild
  7. E. Hoigaard, “Smooth Coffeescript”: http://autotelicum.github.com/Smooth-CoffeeScript/
  8. Alex MacCaw, “The Little Book on Coffeescript”: http://arcturo.github.com/library/coffeescript/
DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 5 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