søndag 23. september 2012 Polyglot JavaScript CoffeeScript Lisp
JavaScript er som alle vet et viktig språk, men syntax-messig har det sine utfordringer. For godt over et år siden fant jeg det populære språket CoffeeScript, som kompileres ned til JavaScript. Det vil si at det i praksis kan erstatte JavaScript-utvikling.
Men det finnes mange slike språk som forsøker å gjøre JavaScript-koding mindre smertefullt. I denne blogposten har jeg tatt litt JavaScript og kodet det samme i tre slike språk: CoffeeScript, LispyScript og PogoScript. Alle disse språkene er plugins til node.js, og er enkle å komme igang med. Målet var å illustrere de ulike syntaksene, og se om noen av dem er "bedre" enn andre.
Koden er en løsning for å finne poengscoren for en bowlingserie. Serien er representert som et array av kast – dvs. av hvor mange sjegler som ble felt av kastet. Løsningen definerer to funksjoner: en som finner poengscoren til en frame (omgang), og en som looper over hele spillet og samler opp totalscoren. Spill-arrayet "spises opp" underveis i denne løsningen – dette er altså ikke funksjonell programmering, her muterer jeg rått og hemningsløst.
var frameScore = function(rolls) { if(rolls[0] === 10) return rolls.shift() + rolls[0] + rolls[1]; if(rolls[0] + rolls[1] === 10) return rolls.shift() + rolls.shift() + rolls[0]; return rolls.shift() + rolls.shift(); }; var score = function(rolls) { var score = 0; for(var i = 0; i < 10; i++) score += frameScore(rolls); return score; }; var gameZeros = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], gameRandom = [10,8,2,5,5,7,3,10,10,7,1,10,10,10,10,8], gamePerfect = [10,10,10,10,10,10,10,10,10,10,10,10]; console.log(score(gameZeros)); // => 0 console.log(score(gameRandom)); // => 213 console.log(score(gamePerfect)); // => 300
I de siste linjene definerer jeg tre test-omganger, beregner poengsummen, og skriver den ut.
Disclamer: Koden er en modifisert utgave av Karl Westin's Bowling Game Kata-løsning som man kan finne på Github.
Det finnes flere Lisp-varianter som kompilerer til JavaScript. Den mest kjente er kanskje ClojureScript. Et annet heter Sibilant, og det ser veldig egentlig veldig bra ut. Men jeg har valgt språket LispyScript, som i alle fall for øyeblikket er en veldig enkel Lispifisering av JavaScript.
Igjen har jeg de to samme funksjonene – jeg har forsøkt å løse oppgaven så likt som den orginale løsningen som mulig.
(var frameScore (function (rolls) (if (= 10 rolls[0]) (+ (rolls.shift) (+ rolls[0] rolls[1])) (if (= 10 (+ rolls[0] rolls[1])) (+ (rolls.shift) (+ (rolls.shift) rolls[0])) (+ (rolls.shift) (rolls.shift)))))) (var score (function (rolls) (loop (i score) (0 0) (if (< i 10) (recur ++i (+ score (frameScore rolls))) score)))) (var gameZeros [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]) (var gameRandom [10,8,2,5,5,7,3,10,10,7,1,10,10,10,10,8]) (var gamePerfect [10,10,10,10,10,10,10,10,10,10,10,10]) (console.log (score gameZeros)) ; => 0 (console.log (score gameRandom)) ; => 213 (console.log (score gamePerfect)); => 300
En utfordring i forhold til denne oppgaven er at LispyScript ikke har noen normal for-løkke. Jeg valgte å bruke en rekursiv loop-makro med en teller (i) som jeg inkrementerer og kontrollerer underveis i score-funksjonen. At pluss-funksjonen bare kunne ta to argumenter gjorde også at frameScore ble noe mindre oversiktelig enn jeg hadde håpet.
Konklusjon: Jeg er glad i Lisp, og kan tenke meg å bruke det for JavaScript-utvikling – men for oppgaver som denne kom LispyScript til kort. Språket trenger flere utvidelser (makoer) for å bli elegant, men bare det at det i det hele tatt finnes makro-støtte gjør det hele interessant.
CoffeeScript har jeg vist frem før, og jeg liker det veldig godt. Spesielt er det kraftfullt med sine list comprehensions – noe som gjorde score-funksjonen du ser nedenfor ganske elegant og kompakt (noen vil kanskje si kryptisk, men man kan ikke gjøre alle fornøyde). Indenteringsreglene i CoffeeScript gjør også koden enklere å skrive enn både JavaScript og LispyScript-variantene.
scoreFrame = (rolls) -> if rolls[0] == 10 rolls.shift() + rolls[0] + rolls[1] else if rolls[0] + rolls[1] == 10 rolls.shift() + rolls.shift() + rolls[0] else rolls.shift() + rolls.shift() score = (rolls) -> (scoreFrame rolls for _ in [0..9]) .reduce (t, s) -> t + s gameZeros = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] gameRandom = [10,8,2,5,5,7,3,10,10,7,1,10,10,10,10,8] gamePerfect = [10,10,10,10,10,10,10,10,10,10,10,10] console.log score gameZeros # => 0 console.log score gameRandom # => 213 console.log score gamePerfect # => 300
Konklusjon: Fortsatt den mest realistiske JavaScript-erstatningen. Koden er fin, men vil føles fremmed for dem som bare har erfaring med C-lignende språk.
Jeg avslutter med det som kanskje er det mest interessante av disse språkene; PogoScript er et språk som legger ekstra vekt på lesbarhet, og som kan være veldig egnet til å skrive domenespesifike språk. Dette kommer nok ikke så godt frem overalt i koden min, men du kan se noen interessante ting som for eksempel at variabelnavn kan inneholde space. Funksjonsnavn inneholder også sine parametre (i paranteser), og disse kan plasseres hvor som helst i navnet. Dermed kan du lage funksjoner som heter ting som for eksempel "move the file (filename) to (directory)". Noe lignende har jeg aldri sett i noe annet språk.
score of next frame in (rolls) = if (rolls.0 == 10) rolls.shift() + rolls.0 + rolls.1 else if ((rolls.0 + rolls.1) == 10) rolls.shift() + rolls.shift() + rolls.0 else rolls.shift() + rolls.shift() score (rolls) = result = 0 for (i = 0, i < 10, i = i + 1) result = result + score of next frame in (rolls) result game zeros = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] game random = [10,8,2,5,5,7,3,10,10,7,1,10,10,10,10,8] game perfect = [10,10,10,10,10,10,10,10,10,10,10,10] console.log (score (game zeros)) // => 0 console.log (score (game random)) // => 213 console.log (score (game perfect)) // => 300
Slik jeg forstår det er PogoScript veldig utvidbart; indentering definerer en ny kodeblokk, og denne eksekveres av funksjonen kodeblokken sendes til. Hvis du har erfaring fra Lisp, Rebol, Ruby eller Io skjønner du kanskje hva jeg mener. I praksis betyr dette at språket har markoer. For-løkken i score-funksjonen er til eksempel egentlig ingen for-løkke i vanlig forstand, men en funksjon som heter for.
Konklusjon: PogoScript er nok ikke et språk jeg ville valgt som en erstatter for JavaScript. Men det er et interessant språk å studere nærmere, og det kan være aktuelt om man en elle rannen gang har behov for å skrive ekstremt lesbar kode.