CoffeeScript: řádně oslazený JavaScript

Milovníci Pythonu, Ruby či Lispu by měli zpozornět: CoffeeScript jim bude když ne sympatický, tak minimálně povědomý. Pojďme se podívat, čím si tento jazyk získal pozornost, proč patří na GitHubu mezi „most interesting“ a proč se líbí lidem z 37signals – čeká nás stručné seznámení s CoffeeScriptem.

Seriál: CoffeeScript (2 díly)

  1. CoffeeScript: řádně oslazený JavaScript 2.12.2010
  2. CoffeeScript: druhá dávka steroidů pro vaše skripty 8.12.2010

Předmluva – proč se o CoffeeScript zajímat?

Když jsem se sám učil JavaScript, připadal mi nejprve jako špatně udělaná nápodoba C: syntaxe je člověku povědomá, ale spousta věcí mu připadá nedotažených či nesmyslných. Touto fází si projde snad každý, kdo zná C-like jazyky a setká se s JavaScriptem. Ostatně i v komentářích na Zdrojáku se lze často setkat s lidmi, kteří na JavaScript velmi urputně nadávají. Připadá jim, že JavaScript je neschopný spatlaný jazyk, co vyzývá svou „nepořádností“ k bastlení kódu a podporuje programátorská neřádstva. Pokud nedáte JavaScriptu druhou šanci, dost možná na této úrovni poznání zůstanete a každé setkání s ním bude pro vás utrpením (že musíte používat takovou hrůzu, zlatá Java, zlaté C++, …)

Při druhém přiblížení, po zkušenosti s jQuery a Prototype, jsem se naučil simulovat třídy funkcemi. JavaScript mi v tu chvíli připadal jako celkem přijatelný jazyk, i když poměrně výstřední (čti: z nějakého důvodu nedodržující mainstreamové zvyklosti). A pak jsem si jednoho dne uvědomil, že se na JavaScript pořád dívám ze špatného směru – jako na OO skriptovací nástroj, jako na bratrance skriptů v BASHi, v perlu, v PHP… Stačilo se podívat z jiného směru, třeba od Lispu (co jsem od konce 80. let nepoužil). A pak jsem to uviděl a vše do sebe začalo zapadat…

Jsem přesvědčen o tom, že pokud budete na JavaScript hledět jen jako na objektově orientovaný skriptovací jazyk, bude vám stále připadat nedomyšlený a nesmyslný. A stejně tak věřím, že CoffeeScript může vnímavému člověku právě ve změně pohledu pomoci. I proto vznikl tento miniseriál, který si klade za cíl představit zajímavý nástroj, postavený nad JavaScriptem, a ukázat tak možnosti samotného JS, které zůstávají při běžném pohledu skryté.

Úvod

Na co návrháři JavaScriptu mysleli? Jako vážně – když už navrhujete jazyk, který má funkce prvního řádu, proč je v něm, proboha, potřeba psát klíčové slovo function? Lisp je zaflákaný slovem lambda, ale to je proto, že to je zkrátka starý jazyk a nic lepšího je tehdy nenapadlo. Asi i proto mají ty makra, která mají – cokoli, jen aby nemuseli psát stále dokola lambda. píše v nadsázce Piers Cawley. Jeho slova jako by vyslyšel Jeremy Ashkenas a téměř na den přesně před rokem (13. prosince 2009) publikoval na GitHubu první verzi svého jazyka s komentářem „Initial commit of the mystery language.“ Ze záhadného jazyka se během roku vývoje stal CoffeeScript (zkraťme si jej na CfS), dospěl do verze 0.9.5 a na Vánoce je oznámena první „ostrá“ verze (1.0).

CoffeeScript je, prakticky vzato, nadstavba nad JavaScriptem, která uživatelům JS přináší spoustu „syntaktického cukru“, inspirovaného Pythonem a Ruby. Nabízí čistší funkcionální zápis a zajišťuje, že výsledný kód používá správné konstrukce. Zápis může připadat někomu překomplikovaný, jinému naopak krásně čistý… Do které skupiny patříte, si zjistíte jednoduchým testem:

dvojmoc = (x) -> x * x
soucet = (xs) ->
  r = 0
  (r = r + x) for x in xs
  r

Pokud vám kód připadá jasný, logický a pochopitelný, je CoffeeScript přesně pro vás!

Použití CoffeeScriptu

Překladač CoffeeScriptu je běžný JavaScriptový program. (Ve skutečnosti je od verze 0.5 napsán sám v CoffeeScriptu a „přeložen sám sebou“ do JS.) Můžete jej použít dvěma způsoby – buď jej můžete spustit v prostředí Node jako běžnou aplikaci pro příkazový řádek (instalace pomocí npm install coffee-script), nebo jej můžete spouštět přímo v prohlížeči. V prohlížeči jej můžete spustit jednak jako klasický překladač, který vezme zdrojový text a vrátí výsledek (funkce CoffeeScript.compile()), jednak jej můžete použít v běžném tagu script s typem nastaveným na text/coffeescript. (V tomto případě je ale důležité si uvědomit, že skripty budou provedeny v jiném kontextu než kdyby byly přímo zapsány v tagu script; nejen kvůli tomu se takové použití nedoporučuje – i když je možné.)

Překladač CfS můžete otestovat na stránkách CoffeeScriptu (pod odkazem TRY COFFEESCRIPT). V překladači vidíte i použití pomocí text/coffeescript, i spolupráci s jinými knihovnami.

Základy CoffeeScriptu

Ne, nebudeme zabíhat do úplných základů programování, předpokládáme, že čtenáři programovat umí, jen je zajímá, co nového a jiného na ně CfS má. Výklad vychází z Language Reference, kde si můžete příklady rovnou i vyzkoušet.

Zápis bloků a funkcí

CoffeeScript se inspiroval u Pythonu a používá stejný způsob zápisu bloků pomocí odsazení. Úvodní mezery na řádku jsou tedy důležité, označují zanoření bloku. CfS nemá složené závorky k označování bloků, místo nich použijte odsazení. Stejně tak nemusíte psát středníky za příkazem, výrazem či funkcí; středník použijete pouze v případě, že chcete na jednom řádku uvést např. víc volání funkcí.

U volání funkce s parametry není nutné použít závorky – parametry prostě následují za jménem funkce: print "Ahoj" a pokračují až do konce řádku nebo bloku. Volání funkce bez parametrů je potřeba zapsat se závorkami.

Funkce

Funkce jsou definovány jednoduše, viz příklad výše: zadáte seznam parametrů, šipku a tělo funkce.

dvojmoc = (x) -> x * x
trojmoc = (x) -> x * dvojmoc x
# jiná možnost:
trojmoc = (x) -> dvojmoc(x) * x
# zkuste si, co by se stalo, kdybyste vynechali závorky u dvojmoc(x)...

Parametrům funkce můžete nastavit i výchozí hodnotu – zápis je očekávatelný:

nalij = (co, kam = "do sklenice") ->
  "Nalil jsi #{co} #{kam}"
nalij "pivo"

Oblast platnosti proměnných

Proměnné jsou v CfS vždy definované v tom bloku, kde jsou použité. Pokud existuje už proměnná stejného jména v nadřízeném bloku, je použita ta. Příklad:

vnejsi = 1
funkce = ->
  vnitrni = 2
  vnejsi = 3
vnitrni = funkce()

Po překladu získáme tento kód:

var funkce, vnejsi, vnitrni;
vnejsi = 1;
funkce = function() {
  var vnitrni;
  vnitrni = 2;
  return vnejsi = 3;
};
vnitrni = funkce();

Vidíme, že ve funkci není proměnná vnejsi deklarována jako lokální, ale je místo toho použita globální. Proto buďte opatrní při psaní vnořených funkcí, abyste nepoužili jméno proměnné z nadřazeného blo­ku.

Řetězce

Řetězce se v CfS zapisují jako v jiných jazycích, tj. do uvozovek či apostrofů. Podobně jako v PHP zde platí, že řetězec zapsaný v apostrofech je konstantní, v řetězci zapsaném v uvozovkách proběhne nahrazení proměnných a výpočet výrazů, pokud jsou zapsané jako  #{...}.

"Třikrát pět je #{3 * 5}"

Řetězce mohou být zapsány přes více řádků, pokud jsou správně odsazeny:

lipsum = "Lorem ipsum dolor sit amet, consectetur adipisicing elit,
  sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
  ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
  ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
  voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
  occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit
  anim id est laborum."

Pokud chcete zapsat řetězec včetně odsazení, apostrofů i uvozovek, můžete použít syntaxi heredoc:

html = '''
  <strong>
  CoffeeScript!
  </strong>
  '''

Pokud místo apostrofů použijete uvozovky, bude fungovat doplňování výrazů, viz výše.

Podobným zápisem můžete vložit do textu i víceřádkový komentář – ten zůstane i ve výsledném kódu. Je tedy ideální např. pro vložení licenčních informací na začátek kódu:

###
CoffeeScript Compiler v0.9.5
Released under the MIT License
###

Vše je výraz

Možná jste si všimli, že v předchozích příkladech v definicích funkcí nebyl použit příkaz return. Funkce zkrátka vrátí hodnotu posledního vyhodnoceného výrazu. Přitom výrazem může být téměř cokoli – výraz v běžném slova smyslu, přiřazení nebo příkaz.

Pokud použijeme přiřazení coby výraz, postará se překladač CfS o to, aby proměnné byly správně deklarovány, i když jsou použity prvně:

sest = (jedna = 1) + (dva = 2) + (tri = 3)

Výsledný JavaScript vypadá takto:

var dva, jedna, sest, tri;
sest = (jedna = 1) + (dva = 2) + (tri = 3);

Blok příkazů je taky výraz, hodnotou je hodnota posledního výrazu v něm.

CoffeeScript je principem „vše je výraz“ prolezlý skrznaskrz, takže i věci, které by v běžném Javascriptu byly příkazy, dokáže zpracovat jako výraz – zabalením do closure. (S výjimkami tam, kde to nemá smysl, např. break či continue.) S příklady se ještě setkáme. Na jednu stranu lze tak dělat poměrně kuriózní konstrukce, jako např. použít blok try-catch coby výraz, ale lze to využít i šikovně – například použít foreach na místě pole:

globals = (name for name of window)[0...10]

Objekty a pole

Zápis objektových literálů či polí je podobný JSON – tedy objekty se zapisují do složených závorek, pole do hranatých.

pole = [1, 1, 2, 3, 5, 8]
bod = {x: 10, y: 20}

Pole může být (s odsazením) i přes více řádků, objekt zase může být zapsán v notifikaci podobné YAMLu:

ctverec = [
  1, 1, 1
  1, 0, 1
  1, 1, 1
]
znalosti =
  php:
    uroven: "pokrocily"
    praxe: 8
  html:
    uroven: "zacatecnik"
    praxe: 3

Příjemná vlastnost CfS je, že nemusíte dávat pozor na klíčová slova. V  JavaScriptu nelze zapsat vlastnost objektu, pokud se jmenuje stejně jako klíčové slovo (třeba „class“), přímo, musí být v uvozovkách. jQuery konstrukci

$('#element').attr({"class":"hidden"});

zapíšeme v CoffeeScriptu jako

$('#element').attr class: 'hidden'

Všimněte si, že lze bez problémů používat i funkce z existujících knihoven, spolupráce s jQuery a podobnými je v CfS naprosto přirozená a bezproblémová.

Jazykové konstrukce

Jazykové konstrukce CoffeeScriptu nepřinášejí žádné zásadní novinky oproti JavaScriptu – jen spoustu syntaktického cukru inspirovaného jinými jazyky (Ruby, Python), díky němuž se kód blíží běžné angličtině.

Podmínky

Příkaz if lze zapisovat bez závorek okolo podmínky a bez složených závorek okolo bloku (víceřádkové bloky se opět vyznačují pomocí odsazení). Lze jej zapsat i v postfixové notaci, tedy až za příkaz. Lze jej použít (viz předchozí pasáž „Vše je výraz“) i jako výraz, který je přeložen pomocí ternárního operátoru  ?:.

vezmi kulich if zima
#alternativa s unless - význam jako if not...
vezmi kulich unless teplo
if prsi
  destnik = 1
  plastenka = 1
else
  slunecniBryle = true
auto = if penize then "Mercedes" else "Žádné"

Slovo „then“ umožňuje oddělit podmínku od hodnoty v případě, že chceme vše zapsat na jeden řádek.

CoffeeScript umožňuje použití zřetězených podmínek, tedy například:

optimum = 18 < teplota < 23

Podmíněné přiřazení a existenční operátor

Hodí se všude tam, kde chceme proměnné přiřadit hodnotu, pokud ještě žádnou nemá (nebo má nulovou):

options or= defaults

Výsledkem bude výraz options || (options = defaults); Obdobně lze použít i operátor  and=.

Předchozí přiřazení proběhne i v případě, že proměnná options obsahuje nulu (false, null). Pokud chceme otestovat, že proměnné nebyla přiřazena žádná hodnota, musíme použít existenční operátor proměnná?. Můžeme tedy napsat

settings = auto if settings? and not auto?

a výsledkem bude JavaScriptový zápis:

var settings;
if ((typeof settings != "undefined" && settings !== null) && !(typeof auto != "undefined" && auto !== null)) {
  settings = auto;
}

Podobně můžeme existenční operátor použít s přiřazením:

odpoved ?= 42

Překlad do JavaScriptu odpovídá známé konstrukci:

typeof odpoved != "undefined" && odpoved !== null ? odpoved : odpoved = 42;

Podobně se lze vyhnout i chybě při přístupu k nedefinovaným vlastnostem objektu, a to operátorem ?.  – příklad:

pozice = window?.geolocation?.position

a výsledek:

var pozice, _ref;
pozice = typeof window != "undefined" && window !== null ? (_ref = window.geolocation) != null ? _ref.position : void 0 : void 0;

Aliasy

CoffeeScript se snaží vyjít programátorům vstříc a udělat ze čtení kódu příjemný zážitek. Nabízí proto další syntaktický cukr v podobě nejrůznějších aliasů.

Už jsme viděli, že operátor && lze nahradit slovem and a operátor || slovem or. Podobně lze negaci (!) zapsat jako not. Operátor porovnání (==) můžeme nahradit slovem is, operátor nonekvivalence slovem isnt. (CoffeeScript překládá == a is jako „===“, != a isnt jako „!==“.)

U logických hodnot platí, že true můžeme zapsat i jako on, popřípadě jako yes. False pak analogicky jako off, nebo  no.

Pro přístup k vlastnostem this můžeme použít zápis pomocí @ – tedy this.value můžeme zapsat jako @value. Více si o objektech povíme v následujícím dí­lu.

Ukázka použití aliasů:

show button if checkbox is on
if rychlost < limit then pridej()

Operátor is lze použít i v konstrukci, v níž zjišťujeme, zda je prvek obsažen v poli hodnot:

colorOK = true if color in ['red', 'green', 'blue']

Smyčky

CoffeeScript nabízí několik typů smyček. Tou základní je while  – od své jmenovkyně v JavaScriptu se liší tím, že je výrazem, jehož hodnotou je pole, které obsahuje výsledky (= poslední vyhodnocený výraz v těle smyčky) pro každý průchod. Tedy například:

pocet = 5
odpocitavani = while pocet--
  "Zbyvajicich dni: #{pocet}..."

Opět máme k dispozici aliasy: until je ekvivalent pro while not, loop pro while(true) (nezapomeňte včas ze smyčky vyskočit). Opět je možná postfixová notace jako u if:

read file until EOF

Výsledek je, jak už víme, pole posledních hodnot pro jednotlivé průchody, takže například emulace funkce file() z PHP, která čte jednotlivé řádky souboru a vrací je coby položky pole, by mohla vypadat teoreticky takto:

radky = (read file until EOF)

For…?

Možná se ptáte, kde je smyčka for či foreach… CfS se tu inspiroval u Pythonu a podobné situace řeší pomocí procházení pole, objektu či rozsahu. Syntax je jednotná: for proměnná in objekt (pole, rozsah)

alert hlaska for hlaska in seznam
r = r + i for i in [0..9]

Smyčka for je opět výraz – pole výsledků jednotlivých iterací:

odpocitavani = ("Zbyvajicich dni: #{pocet}..." for pocet in [5..1])

Bez závorek bude výsledek jiný – podívejte se, v čem a proč (a zjistíte, že je to logické).

Pomocí operátoru in procházíme hodnoty pole či rozsahu. Chceme-li projít vlastnosti objektu, použijeme operátor  of:

lidi = petr: 31, pavel: 29, tomas: 22
alert (jmeno for jmeno of lidi)
alert (jmeno + ":" + rok for jmeno, rok of lidi)

Pokud se podíváte na výsledný kód, vidíte, že CoffeeScript ošetřuje přístup pomocí hasOwnProperty.

var jmeno, lidi, rok, _results, _results2;
var __hasProp = Object.prototype.hasOwnProperty;
lidi = {
  petr: 31,
  pavel: 29,
  tomas: 22
};
alert((function() {
  _results = [];
  for (jmeno in lidi) {
    if (!__hasProp.call(lidi, jmeno)) continue;
    _results.push(jmeno);
  }
  return _results;
}()));
alert((function() {
  _results2 = [];
  for (jmeno in lidi) {
    if (!__hasProp.call(lidi, jmeno)) continue;
    rok = lidi[jmeno];
    _results2.push(jmeno + ":" + rok);
  }
  return _results2;
}()));

Pokud z jakéhokoli důvodu chcete použít opravdu „syrovou“ verzi JavaScriptového for (key in ...), zapište ji jako  for all key, value of ...

Pokračování příště…

V příštím dílu se podíváme na zoubek objektům, třídám, dědičnosti a dalším věcem s tímto tématem spojeným. Ve třetím, posledním, se pak podíváme na některé zajímavosti, spojené s funkcemi vyšších řádů (higher order functions), a na to, jak nám s jejich použitím CoffeeScript pomáhá (ano, ony jsou i v JavaScriptu, ale jsou utopené v Lisp Revenge, tedy v nekonečném počtu závorek). Ukážeme si i některé příklady funkcionálních optimalizací, a pokud do té doby vyjde verze 1.0, tak se samosebou podíváme i na ni.

Pokud máte po dnešním představení CoffeeScriptu dojem, že jde jen o další módní vlnu a zoufalou snahu udělat z JavaScriptu použitelný jazyk, ale nenabízí nic víc než syntaktický cukr, splácaný z několika dalších jazyků, počkejte si na další díly. Budeme sladit JavaScript ještě víc a uvidíte pár věcí, co v C++ ani v Javě nenaprogramujete s takovou elegancí jako v CfS.

Upozornění: Mějte na paměti, že větší volnost znamená i větší zodpovědnost!

Dodatek – CoffeeScript v praxi

A používá to vůbec někdo? Dobrá otázka. Několik příkladů by se našlo: V CoffeeScriptu byl napsán web BusyConf. Používá jej Ars Technica pro svou čtečku pro iPad. Použili ho 37signals pro aplikaci Chalk (zdrojový kód zde – můžete ochutnat, jak taková aplikace vypadá, a srovnat to s odpovídajícím JavaScriptem). Plus desítky malých aplikací, webů a ukázek, u nichž se autoři zmínili, že je v CfS napsali.

Správná otázka není, zda to někdo používá, ale co nám to přinese. A tady už je odpověď zajímavější: CoffeeScript nabízí oproti JavaScriptu několik velmi pěkných výhod – píšete čistší a menší kód (až o třetinu míň textu) bez závorkového pekla. Máte k dispozici „syntaktický cukr“ včetně např. tříd či syntaxe heredoc. Kód je srozumitelnější, a tedy i snáze udržovatelný. Zásadním problémem je debugování – případnou chybu budete hledat kdesi v (naštěstí čitelném) JavaScriptu, a pak budete muset přijít na to, která pasáž ve zdrojovém CfS kódu odpovídá tomu místu. Je to výzva do verze 1.0.

Stay tuned.

Ilustrace: Open Clipart Library

Začal programovat v roce 1984 s programovatelnou kalkulačkou. Pokračoval k BASICu, assembleru Z80, Forthu, Pascalu, Céčku, dalším assemblerům, před časem v PHP a teď by rád neprogramoval a radši se věnoval starým počítačům.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 58

Přehled komentářů

Michal Augustýn Re: CoffeeScript: řádně oslazený JavaScript
Josef Richter Re: CoffeeScript: řádně oslazený JavaScript
jindroush Re: CoffeeScript: řádně oslazený JavaScript
petrblahos Re: CoffeeScript: řádně oslazený JavaScript
xtr Re: CoffeeScript: řádně oslazený JavaScript
Misto flejmu sedni na prdel a pridej ruku k dilu Re: CoffeeScript: řádně oslazený JavaScript
jindroush Re: CoffeeScript: řádně oslazený JavaScript
David Grudl Re: CoffeeScript: řádně oslazený JavaScript
petrblahos Re: CoffeeScript: řádně oslazený JavaScript
dekel Re: CoffeeScript: řádně oslazený JavaScript
petrblahos Re: CoffeeScript: řádně oslazený JavaScript
- Re: CoffeeScript: řádně oslazený JavaScript
jindroush Re: CoffeeScript: řádně oslazený JavaScript
Michal Wiglasz Re: CoffeeScript: řádně oslazený JavaScript
petrblahos Re: CoffeeScript: řádně oslazený JavaScript
jindroush Re: CoffeeScript: řádně oslazený JavaScript
petrblahos Re: CoffeeScript: řádně oslazený JavaScript
JaGa Re: CoffeeScript: řádně oslazený JavaScript
Michal Augustýn Re: CoffeeScript: řádně oslazený JavaScript
Cechjos Re: CoffeeScript: řádně oslazený JavaScript
Roman Re: CoffeeScript: řádně oslazený JavaScript
Antonin Hildebrand CoffeScript je pro me udalost roku
manakmichal CoffeeScript
xBl4d3x Re: CoffeeScript
Martin Malý Re: CoffeeScript
xBl4d3x Re: CoffeeScript
Martin Malý Re: CoffeeScript
Glin Prekladac pythonu do JS
Martin Soušek na jazyce nezáleží
Josef Richter pro zajímavost
mormegil „Funkce první třídy“??
Michal Augustýn Re: „Funkce první třídy“??
Karel Funkce bez "return"
Josef Richter Re: Funkce bez "return"
Michal Augustýn Re: Funkce bez "return"
JS Re: Funkce bez "return"
Ladislav Thon Re: Funkce bez "return"
pepiino CoffeScript se líbí
dekel Re: CoffeScript se líbí
Pavel Křivánek Re: CoffeScript se líbí
Dekel Re: CoffeScript se líbí
pepiino Re: CoffeScript se líbí
MyOwnClone Re: CoffeScript se líbí
Michal Augustýn Re: CoffeScript se líbí
Martin Malý Re: CoffeScript se líbí
hroch32 Re: CoffeScript se líbí
Radek Miček Re: CoffeeScript: řádně oslazený JavaScript
juzna Pekne!
David Grudl Debugování
Satai Re: Debugování
imploder Re: Debugování
xmanas Programátorská ješitnost
Daniel Steigerwald ekosystém
Martin Malý Re: ekosystém
josefrichter Re: ekosystém
Daniel Steigerwald Re: ekosystém
Daniel Steigerwald Re: ekosystém
Přezdívka Chmm
Zdroj: https://www.zdrojak.cz/?p=3379