CoffeeScript, een mooie manier om JavaScript te schrijven

14 March 2012 23:00 Kevin Tuhumury Algemeen, CoffeeScript, Javascript, Ruby

JavaScript is niet meer weg te denken uit de browser. Wanneer men het over frontend werk heeft, komt deze scripting taal al snel ter sprake. Dat is niet zonder reden. De laatste jaren is JavaScript steeds populairder geworden door het gebruik ervan in Rich Internet Applications, door de komst van Ajax en door frameworks zoals jQuery. De prototype-gebaseerde taal is mede door frameworks als jQuery eenvoudiger en aantrekkelijker geworden voor het grote publiek. In de vorm van Node.js is er zelfs een sprong gemaakt van client-side naar server-side.

Het is een belangrijk onderdeel van frontend development en is vrijwel onmisbaar geworden. Voornamelijk vanwege een zekere je ne sais quoi die het aan de eindgebruiker meegeeft. Vaak in de vorm van een animatie of extra interactiviteit. Hoe dan ook, JavaScript is overal op het web te vinden en zal dat nog voor lange tijd zijn.

Een kwestie van smaak of een onaangename syntax?

Inmiddels is het een volwassen taal die aangenaam in gebruik is, maar door mijn tijd als Rubyist ben ik JavaScript’s interpunctie steeds minder plezierig gaan vinden. Van wat ik om mee heen hoor, ben ik daar niet alleen in. Puntkomma’s, accolades en ronde haken zijn zaken die Rubyisten graag weglaten. Wellicht is het een kwestie van smaak, maar in mijn optiek is code die “Rubyesque” is beter leesbaar en een stuk prettiger te hanteren.

In het artikel wat ingaat op de gedachte achter Rubyesque: “Qu’est-ce que c’est Rubyesque?” kwam al naar voren dat korte zinnen beter leesbaar zijn voor mensen. En dat het mensen zijn die de code moeten onderhouden en begrijpen. De filosofie achter Ruby geeft aan dat het gemaakt is om begrijpbaar te zijn voor mensen en zo beschouw ik de reden voor het ontstaan van CoffeeScript ook. Het is van minder belang dat de gecompileerde JavaScript wat moeizamer te interpreteren is. Waar het wel om gaat is dat de code waar wij als programmeurs mee werken helder is.

Om exact die reden werken Rubyisten graag met Haml (of Slim) en Sass (of Less), die zowel het schrijven als lezen van respectievelijk HTML en CSS versnellen en eenvoudiger maken. Dit bevordert de emotionele gesteldheid van de programmeur. Een programmeur met een goed humeur is beter gemotiveerd en bereikt hierdoor meer. Al met al vergemakkelijken deze syntactisch heldere talen het werk enorm.

De vraag is alleen of hetzelfde valt te bereiken met JavaScript als output. De titel van dit artikel is eigenlijk al een voorbode op deze vraag. De inmiddels twee jaar oude (of eigenlijk jonge) CoffeeScript is hier voor in het leven geroepen. CoffeeScript doet in z’n meest pure vorm namelijk precies hetzelfde voor JavaScript, wat o.a. Haml en Sass voor HTML en CSS doen. In essentie helpt CoffeeScript een programmeur de af en toe wat lastige JavaScript eenvoudiger te omschrijven.

Waarom CoffeeScript?

CoffeeScript is feitelijk gezien een syntactische herschrijving van JavaScript. De kern is hetzelfde, alleen zit er een andere – meer leesbare – schil omheen. Het leest als Ruby of Python, maar compileert naar pure JavaScript. JavaScript die fatsoenlijk geschreven is, best practices volgt, goed leesbaar is (ondanks de toegevoegde interpunctie) en zonder waarschuwingen door JavaScript Lint heen komt.

Ondanks dat JavaScript prototype-gebaseerde overerving biedt, werkt CoffeeScript zoals de meeste objectgeoriënteerde talen met klasse-gebaseerde overerving. De compiler zorgt er voor dat er een nette vertaling naar het prototype-gebaseerde mechanisme geschreven wordt. Gedurende dit proces kunnen syntactische fouten vroegtijdig opgemerkt worden, wat een pluspunt is bij iets wat een extra stap lijkt te zijn. Bovendien streeft het ernaar om JavaScript te verbeteren door het voorzien van consistentie in de gecompileerde code. Ook tracht het de ‘slechte kanten’ van JavaScript te ontwijken, wat gerealiseerd wordt door het toepassen van best practices op de te compileren code en de slechte gewoonten niet in CoffeeScript toe te staan.

CoffeeScript is verder niet gelimiteerd tot de browser. De taal kan immers gebruikt worden in server-side JavaScript implementaties als Node.js. Ook reduceert het gebruik van CoffeeScript de pure JavaScript aanzienlijk, soms zelfs een derde tot de helft van de code. Het levert features als klassen, string interpolatie, semantische aliassen en een groot arsenaal aan shortcuts.

CoffeeScript in gebruik

In vergelijking tot JavaScript is het gebruik van ronde haken optioneel in CoffeeScript en worden accolades niet gebruikt. In plaats daarvan wordt er gebruik gemaakt van Python’s indentatie regels. Er is een heldere en schone syntax, die semantische aliassen met zich meebrengt. Zo zijn and, or, is, isnt en unless veel gebruikte keywords, die enorm bijdragen aan de leesbaarheid:

@theLionKing = simba if mufasa is killedBy(scar) and simba.isAdult() and simba isnt killedBy scar

Operators en aliassen

De bovenstaande CoffeeScript one-liner is valide, zolang de variabelen en methoden isAdult() en killedBy() gedefinieerd zijn. Bij het aanroepen van een methode is het gebruik van ronde haken nodig, tenzij er één of meerdere argumenten meegestuurd worden. In een situatie waarbij de leestekens niet gebruikt worden, zou de compiler op zoek gaan naar een variabele genaamd isAdult, niet naar de methode.

De eerste aanroep naar de methode killedBy() heeft een argument, wat omringd wordt door ronde haken. De reden hiervoor is eenvoudig. Het heeft te maken met ambiguïteit. Indien deze ronde haken weggelaten worden, zou de compiler een ander statement naar JavaScript compileren dan verwacht wordt:

if (mufasa === killedBy(scar && simba.isAdult() && simba !== killedBy(scar))) {
  this.theLionKing = simba;
}

In plaats van wat de bedoeling was van de one-liner:

if (mufasa === killedBy(scar) && simba.isAdult() && simba !== killedBy(scar)) {
  this.theLionKing = simba;
}

Het is fijn om interpunctie weg te laten, maar af en toe ontkom je er niet aan om toch een deel te behouden. Het is van belang om het juiste resultaat in de gecompileerde JavaScript te krijgen. Let dus goed op waar interpunctie wel en niet weggelaten kan worden.

Thin arrow en shortcuts

De vertaalslag naar menselijke taal in de CoffeeScript hierboven is direct te maken, zelfs door niet-programmeurs. Er zijn al diverse vergelijkingen met Ruby gemaakt, zo ook het feit dat Ruby code Rubyesque is als het helder is, maar dat is het ook, wanneer het kort en bondig is. CoffeeScript heeft een overvloed aan shortcuts en speelt hier dan ook prima op in:

previousKings = [ mufasa, scar ]
previousKing = (lion) -> lion in previousKings
previousKing simba

Dit deel loopt na of Simba al eens koning is geweest. De tweede regel is de definitie van de previousKing methode die een argument met de benaming lion accepteert. De thin arrow (->) die hierop volgt is een shortcut voor JavaScript’s function. Hetgeen in de methode bekijkt of het meegegeven argument in de eerder gedefinieerde array zit, dit komt tot stand door de in functie, die te vergelijken is aan Ruby’s include?. In een dergelijke situatie wordt in JavaScript vaak indexOf() aangeroepen op een array. Hieruit komt vervolgens -1 indien de variabele er in voorkomt. Het laatste statement van een methode wordt altijd teruggegeven, in dit geval gaat het hier om de waarde false oftewel Simba is nog niet eerder koning geweest.

Ranges

evilKing = previousKings[1..2]

Ranges zijn geen onbekend fenomeen in CoffeeScript. Indien er voor een range een array of string staat, is deze gelijk aan de slice methode, die we o.a. uit JavaScript kennen. Mocht een range los aangeroepen worden dan wordt er een array aangemaakt. Als er in de range twee punten gebruikt worden, dan is het een range inclusief het laatste getal. Bij drie punten is dat exclusief. De uitkomst van het bovenstaande statement is overigens scar.

Splats…, string interpolatie en wat hyena’s

henchmen: (top, rest...) ->
  "Scar's number one henchman is #{first}. The other hyenas are called: #{rest.join(', ')}"

henchmen "Shenzi", "Banzai", "Ed"

Ook splats behoren tot de mogelijkheden in CoffeeScript. De methode henchmen ontvangt in eerste oogopslag twee argumenten, maar niets is minder waar. Het tweede argument is een splat, wat inhoudt dat alle argumenten die na de eerste worden meegezonden, samengevoegd worden in een array. In dit voorbeeld is ook te zien hoe string interpolatie in JavaScript werkt. Het is niet langer nodig om twee of meerdere strings aan elkaar te plakken met een +. Ook hier is Ruby van grote invloed geweest op de syntax.

Ervoor of erachter?

huntDown prey for prey in ["zebra", "antelope", "gazelle"] if lunchTime()

Voor elke prey in de array met prooien wordt de methode huntDown met als argument prey aangeroepen, echter alleen als het tijd is om te lunchen. In de one-liner is gebruik gemaakt van een iterator als achtervoegsel, zo ook het if statement achter de zin. Beiden zijn shortcuts van hun gebruikelijke vorm.

De existentiële operator

De existentiële operator ? die vergelijkbaar is met Ruby’s nil? geeft true terug tenzij een variabele null of undefined is. Ook deze operator draagt enorm bij aan de leesbaarheid:

lunchTime = true if hungry? and not sleeping?

Het volgende is te begrijpen, maar niet in één oogopslag te overzien:

if ((typeof hungry !== "undefined" && hungry !== null) && !(typeof sleeping !== "undefined" && sleeping !== null)) {
  lunchTime = true;
}

Als de variabelen hungry en sleeping niet gedefinieerd zijn, dan is de waarde van lunchTime undefined. Door het volgende toe te voegen onder de bovenstaande regel CoffeeScript wordt de standaard waarde van lunchTime false.

lunchTime or= false

De werking hiervan is eigenlijk ‘gebruik de waarde van lunchTime als die er is, anders false‘. Deze operator is gelijk aan Ruby’s teapot operator: ||=.

Prototypes of klassen?

class Animal

  constructor: (@name, @age) ->

  isAdult: (ageOfMaturity = 5) ->
    @age >= ageOfMaturity

  hunt: ->
    @hungry() and not @sleeping()

class Lion extends Animal

  isAdult: ->
    super 3

CoffeeScript heeft de beschikking over klasse-gebaseerde overerving, de Lion klasse hierboven erft dan ook alles van de Animal klasse over. De Lion klasse heeft geen behoefte aan een andere werking van de constructor van zijn superklasse, deze kan daarom weggelaten worden. In de constructor van de Animal klasse wordt tweemaal gebruik gemaakt van een handige shortcut in CoffeeScript. Door het @-teken voor het argument te plaatsen, zorgt de compiler automatisch voor de opslag ervan in instantie variabelen. In het voorbeeld wat volgt is de gecompileerde constructor te zien.

In het CoffeeScript voorbeeld is ook te zien dat standaard waarden voor argumenten gedefinieerd kunnen worden. Ook valt te zien dat de isAdult methode in de Animal klasse overschreven wordt door die van de Lion klasse. De gecompileerde JavaScript is zoals hieronder te zien valt een stuk uitgebreider dan de paar regels CoffeeScript die er aan voorging.

var Animal, Lion,
  __hasProp = Object.prototype.hasOwnProperty,
  __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };

Animal = (function() {

  function Animal(name, age) {
    this.name = name;
    this.age = age;
  }

  Animal.prototype.isAdult = function(ageOfMaturity) {
    if (ageOfMaturity == null) ageOfMaturity = 5;
    return this.age >= ageOfMaturity;
  };

  Animal.prototype.hunt = function() {
    return this.hungry() && !this.sleeping();
  };

  return Animal;

})();

Lion = (function(_super) {

  __extends(Lion, _super);

  function Lion() {
    Lion.__super__.constructor.apply(this, arguments);
  }

  Lion.prototype.isAdult = function() {
    return Lion.__super__.isAdult.call(this, 3);
  };

  return Lion;

})(Animal);

De hieraan voorgaande voorbeelden waren nog enkel een klein aantal verbeteringen ten opzichte van JavaScript. Wil je nog meer weten? Kijk dan eens op de officiële website.

Zijn er dan helemaal geen nadelen?

Of er daadwerkelijk nadelen zijn valt te bezien, wellicht zijn er wat minpunten op te noemen die het op een mooiere manier schrijven van JavaScript met zich meebrengt. Denkbaar is de extra stap die in het ontwikkelproces plaatsvindt, of dit als programmeur nu wenselijk is of niet. CoffeeScript dient zoals duidelijk is geworden, gecompileerd te worden naar JavaScript. Dit minpunt, als dat al zo genoemd kan worden, is verwaarloosbaar daar het voor een extra controle op syntactische fouten zorgt.

Velen menen dat CoffeeScript een hoge leercurve heeft, zelf ben ik ervan overtuigd dat het toch redelijk snel opgepakt kan worden. De syntax is helder en eenvoudig. Het weglaten van interpunctie kan voor programmeurs die uit een taal komen anders dan Ruby of iets vergelijkbaars lastig zijn, maar na verloop van tijd went dat wel.

Het lastig kunnen debuggen in CoffeeScript zou daarentegen een terecht nadeel kunnen zijn, maar ook hier is wat voor te zeggen. Een flinke statement in CoffeeScript heeft behoorlijk wat gecompileerde code als gevolg, maar CoffeeScript genereert voornamelijk leesbare JavaScript. Met nadruk op voornamelijk, want de JavaScript code die gecompileerd wordt bij het gebruik van klasse-gebaseerde overerving is redelijk complex. Afgezien daarvan en wat interpunctie zal de JavaScript code vrijwel één-op-één overeen komen met zijn niet-gecompileerde uitvoering. Variabelen blijven behouden en krijgen geen ingekorte versie, wat vaak voorkomt bij JavaScript compressors. Van belang is wel dat de geschreven CoffeeScript sprekend is. Hoe groter de helderheid van de variabelen is, hoe beter de leesbaarheid van de pure JavaScript zal zijn.

De runtime fouten in de JavaScript code zijn hierdoor prima te herleiden naar zijn CoffeeScript equivalent. Het enige minpunt wat potentieel nog zou kunnen bestaan is het feit dat bij het debuggen JavaScript kennis vereist is.

Hoe ga ik aan de slag?

Het proces om CoffeeScript te gebruiken is eenvoudig:

  1. Schrijf code in een .coffee bestand.
  2. Compileer het .coffee bestand naar een .js bestand.
  3. Voeg het .js bestand toe aan een webpagina.

Het lijken drie korte en eenvoudige stappen, maar de tweede stap is wat uitgebreider dan de bovenstaande lijst doet vermoeden.

Installatie

CoffeeScript levert een Node compiler die JavaScript bestanden genereert. Om deze te installeren is een stabiele versie van Node en de Node Package Manager (npm) vereist. Meer informatie over het installeren van zowel Node als npm, is op de volgende pagina te vinden. CoffeeScript is vervolgens met npm te installeren:

npm install -g coffee-script

Compileren

Na de installatie van CoffeeScript is voor het compileren van .coffee bestanden niet meer nodig dan het volgende commando:

coffee -c foo.coffee

Om niet constant het commando handmatig te hoeven uitvoeren, kan er -w of --watch toegevoegd worden:

coffee -cw foo.coffee

Dit zorgt ervoor dat de compiler monitoort of er wijzigingen zijn. Indien dat het geval is, wordt er opnieuw gecompileerd. Het toevoegen van de JavaScript code aan een applicatie spreekt voor zich, dus waar wacht je op? Pak je favoriete editor en ga aan de slag!

Als het je leven als programmeur eenvoudiger maakt, waarom niet?

CoffeeScript is in vele opzichten een verademing vergeleken met JavaScript. Met name de syntactische laag over JavaScript heen – want dat is het in essentie – zorgt voor betere leesbaarheid en op een indirecte manier voor een vereenvoudiging van het schrijven van betere JavaScript.

Persoonlijk word ik er erg blij van, maar of dat voor jou ook geldt zul je toch echt zelf moeten bepalen. Probeer het een keer en oordeel zelf!

Be Sociable, Share!

Reageer


8 × = tweeendertig

RSS feed for comments on this post · TrackBack URI