CoffeeScript: druhá dávka steroidů pro vaše skripty

Minulý díl ohodnotil jeden z komentujících slovy „CoffeeScript je JavaScript na steroidech“. Dnes si ukážeme, že minulý díl byl vlastně jen lehkou předehrou. Čekají nás třídy, dědičnost a další syntaktický cukr, co zjednodušuje zápis kódu a zvyšuje tak jeho srozumitelnost.

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

minulého článku se v diskusi opět střetly dva pohledy – jeden nadšený z nových možností, druhý skeptický k novinkám. Pojďme se nejprve lehce rozcvičit, než se pustíme do dnešní porce znalostí.

„JavaScript je primitivní jazyk“

(…) stále nevidím důvod migrovat projekty kvalitně napsané např. v Dojo Toolkit s řádově stovkami komplexních JS souborů, každý o stovkách až tisících řádků na CoffeeScript, píše jeden z komentátorů, takže je na místě připomenout, že CoffeeScript nepřišel nahradit ani vytlačit JavaScript. Neznamená to, že je potřeba migrovat nebo něco přepisovat. Naopak – síla je ve spolupráci. CfS je výhodný třeba pro zápis složitějších algoritmů, které jsou v JavaScriptu utopené v závorkách. Nejde o nahrazení jedné technologie jinou, nejde o buď – anebo. CoffeeScript je jen další možnost, kterou můžete využít tam, kde to bude rozumné a pro vás vhodné. Pokud jej nemáte kde využít, nepoužívejte jej – tak prosté je to!

Javascript má rád kdekdo, pretože je príliš jednoduchý (podobne ako Ruby alebo Smalltalk) a pochopí ho aj cvičená opica, a aj ľudia, ktorým robí problém pochopenie zložitejších jazykových konštrukcií, lebo im ani nerozumejú. Javascript toho veľa nedokáže takže sa v ňom ani nedá veľa pokaziť. Možnosti JS sú na úrovni Turbo Pascalu z konca 80tych rokov. Aj Turbo Pascal alebo Basic bol medzi amatérmi obľúbený práve kôli jednoduchosti. Dnes už Basic (VB.NET) umožnuje všetko čo plnohodnotné jazyky, je zložitejší, bežným programátorom laikom robí problém čítanie cudzích zdrojákov, nechú strácať čas štúdiom a preto sa vrátili k primitívnym jazykom ako PHP a JavaScript. CoffeeScript je len syntaktický cukor, pythonovské odsadzovanie medzerami je nezmysel, pretože každý editor odsadzuje inak. píše další komentující, a jeho komentář nám poslouží jako odrazový můstek k dalšímu výkladu o CoffeeScriptu a k věcem, které v TurboPascalu jednoduše udělat nešly. Uvidíme, jaké konstrukce lze v JavaScriptu vytvořit, a navíc si ukážeme, oč je jejich zápis v CoffeeScriptu snazší.

Co zbývá probrat?

Minule jsme si popsali základní konstrukce: Podmínky a podmíněné výrazy, smyčky a pod. Z těch obvyklých zbývají ještě dvě konstrukce, tedy try/catch/finally a switch. Pojďme na ně. Naším průvodcem bude opět Language Reference.

Ošetření výjimek

Použití je stejné jako v JS. Jen zapisujeme bloky odsazením, nikoli složenými závorkami, a celý blok můžeme použít jako výraz v přiřazení, protože vrací hodnotu.

try
  allHellBreaksLoose()
  catsAndDogsLivingTogether()
catch error
  print error
finally
  cleanUp()

Větvení

Příkaz switch má jiný zápis než v C-like jazycích či Pascalu. Celý blok switch obsahuje klauzule when pro jednotlivé hodnoty, a může obsahovat i univerzální klauzuli else, která uvozuje blok, jenž je vykonán, pokud hodnota nesouhlasí s žádnou výše uvedenou.

Klauzule when nemusí končit příkazem break – nejsou automaticky prováděny další jako v C nebo JS. Pokud se nám vejde činnost v klauzuli when na jeden řádek a nechceme vytvářet nový blok, můžeme ji oddělit od klauzule pomocí slova then. V klauzuli může být i víc hodnot, podobně jako v Ruby. No a – jako už v CfS tradičně – i switch  je výraz a má hodnotu.

switch day
  when "Mon" then go work
  when "Tue" then go relax
  when "Thu" then go iceFishing
  when "Fri", "Sat"
    if day is bingoDay
      go bingo
      go dancing
  when "Sun" then go church
  else go work

Vložený JavaScript

Pokud z nějakého důvodu potřebujete do CfS zapsat JavaScript můžete k tomu použít, podobně jako ve shellu, znak „obrácený apostrof“ (`). Například:

pozdrav = `function() {
  return [document.title, "Hello JavaScript"].join(": ");
}`

Rozsahy

V minulém dílu jsme si ukázali u cyklu For i možnost zápisu cyklu „od X do Y“. CfS jej řeší stejně jako třeba Python pomocí tzv. rozsahu (range). Rozsah se zapisuje do složených závorek pomocí známé notace [x..y]  – tedy např. [1..10]. Rozsah [1..4] bude převeden na pole hodnot [1, 2, 3, 4] (a ne, [1..100] nebude převeden na výčet prvků, ale na funkci, která jej vygeneruje).

Zajímavá (či matoucí a nebezpečná, záleží na úhlu pohledu) je možnost zapsat rozsah pomocí tří teček – v takovém případě se vynechá poslední prvek. Užitečné je to např. tehdy, pokud procházíme pole s indexy od nuly – pak můžeme použít  [0...pole.length].

Operace s poli pomocí rozsahů

Pokud se pamatujete na jazyk BASIC na ZX Spectru, vzpomínáte si na jeho operátor TO, který umožňoval vybírat části řetězců, a umožňoval je i měnit. K něčemu podobnému slouží v CfS rozsahy:

pismena = ['b', 'f', 'l', 'm', 'p', 's', 'v', 'z']
kopie   = pismena[0...pismena.length]
stred   = kopie[2..5]
pismena[2..5] = ['x', 'y', 'z', 'q']

U tohoto příkladu dejte pozor – v době psaní tohoto článku byla na stránkách CoffeeScriptu u Try Coffeescript starší verze 0.9.5, která neumí poslední výra zpracovat korektně. Nová verze 0.9.6 funguje bez problémů, vyzkoušet můžete např. v našem překladači, který používá právě aktuální verzi.

Třídy a dědičnost

Ne že by v JavaScriptu nebyly – jsou, ale jsou implementovány jinak, než je obvyklé v jazycích s třídami. O správný (či dokonce o jediný správný) způsob se vedou spory. V JavaScriptu existuje několik knihoven, které simulaci tříd a dědičnosti nabízejí.

CoffeeScript nenechává programátory lačné tříd a dědičnosti na holičkách a nabízí obojí, čímž je odstiňuje od výše odkázaných sporů. Nabízí strukturu class, v níž lze určit jméno třídy, třídu nadřazenou, přiřadit vlastnosti a nadefinovat konstruktor.

class Zvire
  constructor: (@jmeno) ->
  pohyb: (metry) ->
    alert @jmeno + " urazila " + metry + "m."
class Had extends Zvire
  pohyb: ->
    alert "Plazení..."
    super 5
class Gazela extends Zvire
  pohyb: ->
    alert "Trysk..."
    super 45
anna = new Had "Krajta Anna"
beta = new Gazela "Gazela Běta"
anna.pohyb()
beta.pohyb()

Vidíme deklaraci třídy Zvire s konstruktorem a jednou metodou. Konstruktory jsou kvůli snazší reflexi pojmenované, takže v našem příkladu bude this.constructor.name v jednotlivých instancích buď „Had“ nebo „Gazela“. Pokud bychom vytvořili přímo instanci třídy Zvire, měl by v ní konstruktor jméno „Zvire“.

Možná vás zarazilo, že je tělo konstruktoru prázdné a s parametrem se nijak viditelně nepracuje. Minule jsme si v části o aliasech ukazovali zjednodušený zápis this pomocí prefixu @. Pokud tento prefix použijeme u funkce v poli parametrů, vytvoříme tím přiřazení parametru do vlastnosti, tedy např.

  metoda: (@x, @y) ->

bude přeloženo jako 

function(x, y) {
    this.x = x;
    this.y = y;
  };

Pomocí  téhož operátoru můžeme snadno definovat vlastnosti:  @vlastnost: hodnota

Dědičnost je vyznačena pomocí slova extends, volání rodičovské metody lze zařídit klíčovým slovem super. Tedy syntaxe, na jakou jste možná zvyklí z jiných jazyků.

Pro zajímavost, výše uvedený příklad bude přeložen takto:

var Gazela, Had, Zvire, anna, beta;
var __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;
};
Zvire = function() {
  function Zvire(jmeno) {
    this.jmeno = jmeno;
  }
  Zvire.prototype.pohyb = function(metry) {
    return alert(this.jmeno + " urazila " + metry + "m.");
  };
  return Zvire;
}();
Had = function() {
  function Had() {
    Had.__super__.constructor.apply(this, arguments);
  }
  __extends(Had, Zvire);
  Had.prototype.pohyb = function() {
    alert("Plazení...");
    return Had.__super__.pohyb.call(this, 5);
  };
  return Had;
}();
Gazela = function() {
  function Gazela() {
    Gazela.__super__.constructor.apply(this, arguments);
  }
  __extends(Gazela, Zvire);
  Gazela.prototype.pohyb = function() {
    alert("Trysk...");
    return Gazela.__super__.pohyb.call(this, 45);
  };
  return Gazela;
}();
anna = new Had("Krajta Anna");
beta = new Gazela("Gazela Běta");
anna.pohyb();
beta.pohyb();

Je vidět, že „pod pokličkou“ jsou třídy implementovány klasickým JS způsobem, tedy pomocí funkcí a prototypů. CfS nabízí pro usnadnění i operátor :: pro přístup k prototypu

Array::map = -> ...

bude přeloženo jako

Array.prototype.map = function () {...}

Výpustka

Výpustka (splat) naznačuje, že výčet (např. parametrů) není úplný. Lze ji použít jak v definici funkce, tak při jejím volání. Zapisuje se pomocí tří teček ( ...) a její použití ozřejmí nejlépe příklad:

f1 = (a, b...) ->
  alert a
  alert b
f1 10, 20, 30, 40
par = [3,1,4,1,5]
f2 = (a,b,c,d,e) -> a+b+c+d+e
alert f2 par
alert f2 par...
f1 par...

U definice funkce (f1) naznačuje, že funkce může mít víc parametrů, než je použito v definici. „Nadbytečné“ budou předány jako pole. Pokud voláme funkci a předáváme jí pole, můžeme jej pomocí výpustky „rozvinout“ coby seznam parametrů (ukázka u dvou různých způsobů volání f2). Pokud použijeme výpustku jak v definici, tak při volání, rozvine se pole do seznamu hodnot, ten je přiřazen odpovídajícím parametrům, a zbytek je předán v parametru s výpustkou.

Výpustka nemusí být nutně posledním parametrem:

pole = [3,1,4,1,5]
f3 = (a, x..., b) -> x
alert f3 pole...

Funkce f3 přijme libovolný počet parametrů. První bude a, poslední b a zbytek („to mezi“) bude pole x.

Přiřazení struktur

CoffeeScript implementuje návrh přiřazení struktur (destructuring assignment), tedy takový zápis přiřazení, kdy je přiřazováno pole či objekt jinému poli či objektu. V takovém případě překladač obě strany přiřazení rozebere a přiřadí postupně jednotlivé hodnoty jednu po druhé. V nejjednodušší podobě lze toto přiřazení použít např. k prohození hodnot:

[alfa, beta] = [beta, alfa]

Stejně tak lze použít toto přiřazení i u funkcí, co vrací více hodnot:

miry = ->
  [90, 60, 90]
[prsa, pas, boky] = miry()

Přiřadit lze i komplikovanější struktury, a to v libovolné úrovni vnoření:

zamestnanci =
  technik: "Novotný"
  prodejce: "Novák"
  ridic:
    jmeno: "Dvořák"
    auto: [
      "Škoda"
      "benzín"]
{ridic: {jmeno, auto: [znacka, palivo]}} = zamestnanci

Přiřazení pak proběhne podle vyhovujícího vzorku.

I u přiřazení lze použít výpustky – s podobným přiřazováním jsme se setkali v předchozí části o výpustkách. Nepřekvapí, že můžeme zapsat:

pole = [3,1,4,1,5]
[prvni, telo..., posledni] = pole

Regulární výrazy

CoffeeScript nabízí rozšířený zápis regulárních výrazů, které ignorují mezery a odřádkování. Navíc mohou obsahovat komentáře. Takové regulární výrazy jsou označené omezovačem ///. Příkladem může být regulární výraz přímo z překladače CoffeeScriptu:

OPERATOR = /// ^ (
  ?: [-=]>             # function
   | [-+*/%<>&|^!?=]=  # compound assign / compare
   | >>>=?             # zero-fill right shift
   | ([-+:])1         # doubles
   | ([&|<>])2=?      # logic / shift
   | ?.              # soak access
   | .{2,3}           # range or splat
) ///

V přeloženém textu bude vše převedeno na starý dobrý známý regulární výraz:

OPERATOR = /^(?:[-=]>|[-+*/%<>&|^!?=]=|>>>=?|([-+:])1|([&|<>])2=?|?.|.{2,3})/;

Funkčně jde o totéž, z hlediska srozumitelnosti a udržovatelnosti kódu první zápis u většiny lidí pravděpodobně zvítězí.

Které this je teď this?

V JavaScriptu je častým problémem správná představa o tom, co je hodnotou this. Pokud totiž použijete funkci jako callback nebo ji přiřadíte k jinému objektu, ztratí se původní obsah this. Pokud vám není toto chování známé, přečtěte si víc v článku Scope in JavaScript či ve výše odkázaném seriálu Dana Steigerwalda.

V knihovnách, které hojně používají callbacky, jako Prototype či jQuery, je vhodné neztratit kontext objektu, v němž funkci s callbackem používáme. CoffeeScript k tomuto účelu používá definiční operátor „tlustá šipka“ ( =>). Pomocí tohoto operátoru lze zapsat funkci, v jejímž těle se můžeme odkazovat na this, a toto this bude svázáno s kontextem, v němž je definováno.

Využití tento operátor najde všude tam, kde bychom v JavaScriptu používali anonymní funkci jako callback – např. při volání bind() nebo jako parametr funkce each().

Další informace o CoffeeScriptu

Právě jsme se seznámili s hlavními rysy tohoto jazyka. Pokud vás zaujal a chcete další informace, knihovny či příklady, zkuste tento seznam (platný v době psaní článku):

Ochutnávka na příště

Znáte jQuery? Víte, jak se v něm definují obsluhy událostí, např. click? Přece $().click(callback_funkce). V praxi to je třeba

$().click(function(event) {...})

Co když bychom chtěli víceparametrový callback? $("#test1").click(funkce(event, "ahoj")); nefunguje, musíme použít closure.

Ale můžeme si také vytvořit funkci, která nám připraví callback s parametrem. Funkce, která vrátí funkci, použitelnou jako callback.

callback = function(f) {
  return function(event) {
    return f(event, args);
  };
};

V CoffeeScriptu zapíšeme totéž o něco snáz:

callback = (f, arg) ->
   (event) -> f event, arg

Z libovolné funkce akce(event, parametr) pak uděláme obsluhu událostí pro jQuery i s parametrem velmi snadno:

$("#test1").click(callback(akce, 1));
$("#test2").click(callback(akce, 2));

A co když těch parametrů bude víc? Tak si callback zobecněme pomocí toho, co jsme se dnes naučili:

callback = (f, args...) ->
   (event) -> f event, args...

callback je funkce(1), která má jako parametr jméno funkce F a blíže neurčený seznam parametrů. Vrací novou funkci(2), která má jeden parametr (event). Ta zavolá funkci F s parametry event a s předanými argumenty.

A teď si za domácí úkol napište takovou obecnou funkci callback() v čistém JS…

Příště se budeme věnovat dalším funkcím co vrací funkce, funkcím co vrací funkce které vrací funkce, nasimulujeme si Smalltalkové inject: into:, a taky si spočítáme Fibbonaciho posloupnost a ukážeme si, proč každému jasné řešení není dobré řešení.

PS: Domácí úkol podle CoffeeScriptu:

callback = function() {
  var args, f;
  f = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
  return function(event) {
    return f.apply(null, [event].concat(__slice.call(args)));
  };
};

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: 14

Přehled komentářů

... Steroidy
EskiMag Re: Steroidy
Daniel Steigerwald Re: Steroidy
Pavel Křivánek Re: CoffeeScript: druhá dávka steroidů pro vaše skripty
Martin Malý Re: CoffeeScript: druhá dávka steroidů pro vaše skripty
David Grudl Blbý komentáře
Martin Malý Re: Blbý komentáře
imploder Ladění + matoucí switch
Martin Malý Re: Ladění + matoucí switch
imploder Re: Ladění + matoucí switch
Jiří Knesl Re: Ladění + matoucí switch
Čelo hezké
Jiří Knesl Martine!
Radim Daniel Pánek Pěkné
Zdroj: https://www.zdrojak.cz/?p=3383