Propojení světa LISPu se světem JavaScriptu s využitím transpřeklada­če Wisp

3. 12. 2015
Doba čtení: 25 minut

Sdílet

V článku o projektech lua2js a lua.vm.js jsme se seznámili s využitím jazyka Lua na webových stránkách, a to samozřejmě i v prohlížečích podporujících pouze JavaScript. Dnes se budeme zabývat ambicióznějším nástrojem nazvaným Wisp, jehož cílem je propojení zjednodušené varianty Clojure(Scriptu) s JavaScriptem.

Obsah

1. Propojení světa LISPu se světem JavaScriptu s využitím transpřekladače Wisp

2. Wisp versus ClojureScript

3. Instalace Wispu

4. Podporované datové typy

5. Základ práce s kolekcemi

6. Aritmetické funkce

7. Funkce implementující relační operátory, test na identitu objektů

8. Funkce implementující logické operátory

9. Rozhodování založené na formě „if“ a „cond“

10. Způsob zápisu funkcí

11. Obsah druhé části článku

12. Repositář s dnešními demonstračními příklady

13. Odkazy na předchozí části tohoto seriálu

14. Odkazy na Internetu

1. Propojení světa LISPu se světem JavaScriptu s využitím transpřekladače Wisp

V článku Programovací jazyk Lua v roli skriptovacího jazyka pro WWW stránky, v němž jsme se věnovali především popisu projektů nazvaných lua2js a lua.vm.js, jsme se mj. zmínili i o problematice takzvaných transpřekladačů (transcompilers, source-to-source compilers). Připomeňme si, že transpřekladače jsou nástroje sloužící pro překlad algoritmů zapsaných v nějakém zdrojovém programovacím jazyce do zvoleného cílového jazyka (ovšem nikoli do nativního kódu či bajtkódu, to je role běžných překladačů).

Transpřekladače se v informatice používají již po několik desetiletí; například se stále můžeme setkat s nástroji, které převádí kód z nějakého vyššího programovacího jazyka do Céčka, které je dnes s trochou nadsázky chápáno jako „univerzální assembler“. Asi nejznámějším příkladem je nástroj nazvaný web2c, jenž slouží pro transformaci zdrojových kódů TeXu do céčka. Transpřekladače se stávají velmi populární i pro programátory webových aplikací, a to zejména z toho důvodu, že webové prohlížeče nativně podporují většinou pouze JavaScript, který je tak přirozeně cílovým jazykem transpřekladačů (proto se mu také někdy říká „assembler pro web“).

Při použití JavaScriptu jakožto cílového jazyka se transpřekladač většinou spouští jen jednou na vývojářském počítači, takže samotní klienti již mají k dispozici JavaScriptový kód (který může být v závislosti na způsobu překladu čitelný, optimalizovaný či navíc ještě obfuskovaný). Existuje však i druhá možnost, kdy je transpřekladač naprogramován v JavaScriptu a spouštěn přímo ve webovém prohlížeči klientů. Oba přístupy mají své přednosti, ale pochopitelně i nějaké zápory (například tvůrci uzavřených aplikací pravděpodobně budou upřednostňovat první možnost, protože výstupy transcompilerů jsou většinou dosti nečitelné). Z praxe můžeme uvést například následující projekty založené na transpřekladači:

:
# Jazyk či transpřekladač Poznámka
1 CoffeeScript přidání syntaktického cukru do JavaScriptu
2 ClojureScript překlad aplikací psaných v Clojure do JavaScriptu
3 TypeScript nadmnožina jazyka JavaScript, přidání datových typů
4 6to5 transpřeklad z ECMAScript 6 (nová varianta JavaScriptu) do starší varianty JavaScriptu
5 Kaffeine rozšíření JavaScriptu o nové vlastnosti
6 RedScript jazyk inspirovaný Ruby
7 GorillaScript další rozšíření JavaScriptu
8 ghcjs transpřekladač pro fanoušky programovacího jazyka Haskell
9 Haxe transpřekladač, mezi jehož cílové jazyka patří i Java a JavaScript
10 Wisp transpřekladač, jehož popisem se budeme zabývat v dalších kapitolách
11 ScriptSharp transpřekladač z C# do JavaScriptu
12 Dart transpřekladač z jazyka Dart do JavaScriptu
13 COBOL → C transpřekladač OpenCOBOL
14 COBOL → Java transpřekladač P3COBOL

2. Wisp versus ClojureScript

Transpřekladač Wisp slouží k překladu programů vytvořených v programovacím jazyce, který je podmnožinou jazyka Clojure či ClojureScript, do JavaScriptu. Vzhledem k tomu, že i ClojureScript je překládán do JavaScriptu (viz též [1], nabízí se logická otázka, jaký je vlastně mezi ClojureScriptem a Wispem rozdíl. Cílem autorů ClojureScriptu je nabídnout vývojářům plnohodnotnou alternativní implementaci programovacího jazyka Clojure, což se do značné míry daří, samozřejmě s ohledem na fakt, že současné JavaScriptové enginy nenabízí některé vlastnosti, které Clojure využívá (to například znamená omezené využití agentů či futures). ClojureScript je (trans)překládán do JavaScriptu relativně komplikovaným způsobem; výsledek transpřekladu je dále optimalizován a většinou i „minifikován“, takže se ke klientům dostane již značně nečitelná varianta původního algoritmu (optimalizaci a minifikaci lze při tvorbě zakázat). Díky tomu, že ClojureScript podporuje většinu vlastností jazyka Clojure, je možné při vývoji webových aplikací použít prakticky stejný programovací jazyk jak na straně serveru, tak i na straně klienta, čehož některé společnosti s výhodou využívají.

Transpřekladač Wisp je namísto toho navržen takovým způsobem, aby algoritmus zapsaný v podmnožině jazyka Clojure převedl co nejpřímějším způsobem do JavaScriptu, ideálně tak, aby původní algoritmus byl jasně viditelný i ve vygenerovaném kódu (podle názoru autora může být Wisp z tohoto důvodu takřka ideální učební pomůckou vhodnou pro vysvětlení principu funkcionálních jazyků). Wisp ovšem zachovává některé vlastnosti jazyka Clojure, zejména ty vlastnosti, které jsou odvozeny od LISPu – Wisp je stále homoikonický jazyk (což znamená, že kód je reprezentován stejným způsobem jako data a lze s ním i manipulovat jako s daty), nalezneme zde podporu pro TCO (tail call optimization) a taktéž, což je poměrně důležité, podporu pro makra. Další vlastnosti jazyka Clojure ovšem již podporovány nejsou – protože Wisp používá nativní datové typy JavaScriptu, nenalezneme v něm například skutečné neměnné (immutable) datové struktury, transakční paměť (STM) a i podpora pro líné vyhodnocování (lazy evaluation) je prozatím pouze minimální.

3. Instalace Wispu

Na rozdíl od ClojureScriptu nepoužívá Wisp ke své činnosti ani Clojure ani Javu (a tedy ani JVM), požaduje pouze několik JavaScriptových knihoven. Pro instalaci transpřekladače Wispu je vyžadován nástroj npm (node package manager). Pokud tento nástroj ještě na svém systému nemáte, lze ho jednoduše nainstalovat například takto:

sudo apt-get install npm

popř. na distribucích založených na RPM následovně:

sudo yum install npm

Po instalaci zkontrolujeme, zda se nástroj npm skutečně podařilo korektně nainstalovat a nakonfigurovat:

npm -version
1.3.10

Dále je nutné nainstalovat balíček nodejs:

sudo npm install nodejs

Následuje samotná instalace Wispu, která může vypadat následovně:

sudo npm install -g wisp
npm http GET https://registry.npmjs.org/wisp
npm http 304 https://registry.npmjs.org/wisp
...
...
...
npm http 200 https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz
/usr/local/bin/wisp -> /usr/local/lib/node_modules/wisp/bin/wisp.js
wisp@0.10.0 /usr/local/lib/node_modules/wisp
├── base64-encode@1.0.1
├── commander@2.9.0 (graceful-readlink@1.0.1)
└── escodegen@1.7.1 (estraverse@1.9.3, esutils@2.0.2, esprima@1.2.5, source-map@0.2.0, optionator@0.5.0)

Nyní si můžete zkusit spustit REPL Wispu:

wisp

Pokud se vypíše chybové hlášení, že nebyl nalezen spustitelný soubor node, je nutné vytvořit symbolický link pojmenovaný node ukazující na nodejs:

sudo ln -s /usr/bin/nodejs /usr/bin/node

Nyní by již měl být REPL spustitelný:

wisp

Poznámka: v dalším textu budeme Wisp používat skutečně v roli transpřekladače, tedy přibližně následovně:

cat source_file.clj | wisp > target_file.js

4. Podporované datové typy

Programy vytvořené ve Wispu se skládají ze čtyř typů takzvaných forem:

  1. literály (numerické hodnoty, pravdivostní hodnoty, znaky, řetězce; vyhodnocují se samy na sebe)
  2. symboly a takzvaná „klíčová hesla“ (keywords)
  3. složené formy (seznamy, vektory, množiny a mapy, seznamy mají navíc zvláštní postavení kvůli způsobu svého vyhodnocení)
  4. speciální formy (podobají se složeným formám – seznamům, ale interně se vyhodnocují odlišným způsobem)

Popišme si nejdříve jednoduché literály. Mezi ně patří především typ (a současně i hodnota) nil, pravdivostní hodnoty true a false, numerické hodnoty s plovoucí řádovou čárkou, řetězce a znaky:

; "not in list"
nil
 
; pravdivostní hodnoty
true
 
false
 
; numerické hodnoty
42
 
1.2M
 
2/3
 
; znaky
\a
\@
 
; řetězce
"Hello world!"

Po transpřekladu do JavaScriptu můžeme vidět, jakým způsobem se literály přeloží. Zajímavé je, že znaky se přeloží do formy řetězcového literálu:

// "not in list", vyhodnotí se jako "undefined" pomocí operátoru void
void 0;
 
// pravdivostní hodnoty
true;
false;
 
// numerické hodnoty
42;
1.2;
0.6666666666666666;
 
// znaky
'a';
'@';
 
// řetězce
'Hello world!';

V Clojure a taktéž ve Wispu se setkáme i s „keywords“. Překlad tohoto slova je poněkud problematický kvůli jeho dvojímu významu (alespoň v programování), takže se pokusím používat sousloví „klíčová hesla“, protože termín „keywords“ v Clojure/Wispu neznamená, že by se jednalo o rezervovaná klíčová slova jazyka. Klíčová hesla jsou na použití jednodušší než symboly, protože se ve smyčce REPL vyhodnocují samy na sebe a nemůže jim být přiřazena žádná hodnota. Na co se tedy vlastně v praxi tento typ formy hodí? Jedním z důvodů zavedení tohoto typu formy do programovacího jazyka Clojure/Wisp byla podpora pro datového typy (kolekce) mapa, v nichž je možné uchovávat dvojice klíč:hodnota. A jako klíč jsou s výhodou používána právě klíčová hesla, protože jejich hodnotu nelze měnit a navíc se jejich hešovací hodnota může vypočítat pouze jedenkrát. Překladač pozná, že uživatel používá klíčové heslo z toho, že je těsně před ním napsána dvojtečka:

:keyword

Poněkud překvapivě se keywords překládají do řetězců, což je ale dostatečné, protože v rámci jednoho skriptu bude použit identický objekt:

'keyword';

5. Základ práce s kolekcemi

V této kapitole si popíšeme takzvané složené formy, protože právě tyto formy představují, společně s formami speciálními, jeden z nejdůležitějších prvků jazyka Wisp (a podobně i původního LISPu). V tradičním LISPu je složenou formou především seznam (list), což je ovšem jen zjednodušeně zapsaný řetězec takzvaných tečka-dvojic. Koncept tečka-dvojic byl ve Wispu opuštěn, ovšem k seznamům navíc přibyly i další způsoby zápisu složených forem, které se používají (opět v podstatě jako syntaktický cukr) pro zápis následujících datových struktur: vektoru (vector), množiny (set) a mapy (map). Všechny čtyři typy složených forem (budeme je dále nazývat kolekce), ať již se jedná o seznam, vektor, množinu či mapu, jsou z obou stran uvozeny závorkami, přičemž musí být zachována párovost závorek (ke každé otevírací závorce přísluší jedna závorka uzavírací).

V předchozím textu bylo napsáno, že základním typem složené formy je seznam (list), jehož prvky se již tradičně (více než padesát let!) zapisují do kulatých závorek. Pro zápis vektorů (vector) se používají hranaté závorky, mapy (map) využívají závorky složené a množiny (set) taktéž závorky složené, ovšem před otevírací závorkou se musí napsat křížek (hash, #). V následující tabulce jsou vypsány všechny čtyři typy složených forem:

Typ kolekce Zápis (syntaktický cukr)
Seznam (prvky)
Vektor [prvky]
Mapa {dvojice klíč-hodnota}
Množina #{unikátní prvky}

Opět se podívejme na příklady:

; Seznamy
 
; prázdný seznam
'()
 
; seznam čísel
'(1 2 3 4)
 
; seznam řetězců
'("prvni" "druhy" "treti")
 
; seznam "keywords"
'(:prvni :druhy :treti)
 
 
; Vektory
 
; prázdný vektor
[]
 
; vektor čísel
[1 2 3 4]
 
; vektor řetězců
["prvni" "druhy" "treti"]
 
; vektor "keywords"
[:prvni :druhy :treti]
 
; vektor proměnných
[positionX positionY positionZ]
 
 
; Mapa
 
; prázdná mapa
{}
 
; mapování typu string-string
{"prvni" "first" "druhy" "second" "treti" "third"}
 
; mapa s vyhodnocením proměnných
{"X" positionX "y" positionY "z" positionZ}
 
 
; Množina
#{"prvni" "druhy" "treti"}

Překlad do JavaScriptu je u některých kolekcí možná poněkud překvapivý a vede k tomuto závěru: pro reprezentaci dat používejte především vektory a mapy (pro další dvě kolekce je zapotřebí připravit příslušné konstruktory):

// seznamy
list();
list(1, 2, 3, 4);
list('prvni', 'druhy', 'treti');
list('\uA789prvni', '\uA789druhy', '\uA789treti');
 
// vektory
[];
[
    1,
    2,
    3,
    4
];
[
    'prvni',
    'druhy',
    'treti'
];
[
    'prvni',
    'druhy',
    'treti'
];
[
    positionX,
    positionY,
    positionZ
];
 
// mapy
({});
({
    'prvni': 'first',
    'druhy': 'second',
    'treti': 'third'
});
({
    'X': positionX,
    'y': positionY,
    'z': positionZ
});
 
// množiny
set('prvni', 'druhy', 'treti');

6. Aritmetické funkce

Seznamy, tj. jeden ze čtyř podporovaných typů kolekcí, mají v programovacím jazyku Wisp ještě jeden dosti zásadní význam. Samotný program zapsaný ve Wispu není totiž nic jiného než seznam (či seznamy), přičemž prvním prvkem seznamu je jméno funkce a zbylé prvky seznamu jsou chápány jako parametry této funkce.

Vzhledem k tomu, že vyhodnocování seznamů – volání funkcí – představuje důležitou součást programovacího jazyka Wisp, ukážeme si způsob tohoto vyhodnocování na několika demonstračních příkladech, kde je představen prefixový zápis aritmetických operátorů. Povšimněte si především proměnné arity:

; operace rozdílu - druhý argument funkce je odečten od prvního
(- 1 2)
 
; součet řady čísel
(+ 1 2 3 4 5 6 7 8 9 10)
 
; níže uvedený výraz v infixové notaci odpovídá: 1-2-3-4-5....-10:
(- 1 2 3 4 5 6 7 8 9 10)
 
; POZOR - závorky v LISPu nemají mnoho společného
; s vyjádřením priority aritmetických operací
; (nelze je použít tak volně jako například v céčku)
(* (+ 1 2) (+ 3 4))
 
(+ (* 1 2) (* 3 4))
 
; Clojure umí, podobně jako některé implementace LISPu,
; pracovat se zlomky, tj. snaží se racionální
; čísla vyjádřit formou zlomku (ideální jazyk do škol)
; POZOR:
; To však není případ Wispu, ten používá nativní formát
; numerických hodnot JavaScriptu.
(/ 1 2)
 
(/ 1 2 3)
 
; zkusíme výpočet složitějšího zlomku
(/ (+ 1 2) (+ 3 4))
 
; dělení modulo
(mod 10 3)
 
; neracionální (reálná) čísla se vypisují tak, jak to
; známe z ostatních programovacích jazyků (samozřejmě
; v případě speciálních požadavků programátora lze použít
; různé formátovací funkce na úpravu výstupu)
(* 0.3 (/ (+ 1 2) (+ 3 4)))

Výsledek transpřekladu do JavaScriptu:

1 - 2;
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10;
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10;
(1 + 2) * (3 + 4);
1 * 2 + 3 * 4;
1 / 2;
1 / 2 / 3;
(1 + 2) / (3 + 4);
10 % 3;
0.3 * ((1 + 2) / (3 + 4));

Samotný překladač Wispu nedělá rozdíl mezi hodnotou a proměnnou, takže i následující zápis je korektní:

; namísto numerických hodnot lze použít i proměnné
(+ variableA variableB variableC variableD)
(* (+ variableA variableB) (+ variableC variableD))
(+ (* variableA variableB) (* variableC variableD))
(/ (+ variableA variableB) (+ variableC variableD))
(mod sum items)

Tyto čtyři formy se transpřeloží takto:

variableA + variableB + variableC + variableD;
(variableA + variableB) * (variableC + variableD);
variableA * variableB + variableC * variableD;
(variableA + variableB) / (variableC + variableD);
sum % items;

Povšimněte si především toho, že samotný transpřeklad není „otrocký“, ale správně bere v potaz prioritu operátorů v JavaScriptu.

7. Funkce implementující relační operátory, test na identitu objektů

Programovací jazyk Wisp obsahuje i úplnou sadu relačních operátorů, které v závislosti na hodnotách předaných parametrů (operandů) vrací hodnotu true (pravda) či false (nepravda). Zde se Wisp spíše podobá Scheme než klasickému LISPu (s jeho T a nil), což je ostatně jen dobře. Za zmínku stojí i funkce pojmenovaná identical?, která se přeloží na JavaScriptový operátor ===, tj. na test, zda dva objekty jsou skutečně totožné, tj. zda leží na stejném místě v operační paměti:

; porovnání dvou číselných hodnot
; relace "menší než"
(< 1 2)
 
; relace "větší než"
(> 1 2)
 
; relace "menší nebo rovno"
(<= 1 2)
 
; relace "větší nebo rovno"
(>= 1 2)
 
; porovnání dvou výrazů na ekvivalenci
(== 1 2)
(== 1 1)
 
; pozor na rozdíl mezi = a ==
(= 1 2)
(= 1 1)
 
; navíc lze použít i speciální formu identical?
; ta se přeloží na operátor ===
(identical? 1 2)
(identical? [1 2] [3 4])
 
; podvýrazy se nejprve vyhodnotí a posléze se porovnají
; vyhodnocené výsledky (v tomto případě dva atomy)
(== (+ 1 1) (/ 4 2))

Výsledek transpřekladu do JavaScriptu (zajímavé je porovnání vektorů == polí):

1 < 2;
1 > 2;
1 <= 2;
1 >= 2;
1 == 2;
1 == 1;
isEqual(1, 2);
isEqual(1, 1);
1 === 2;
[
    1,
    2
] === [
    3,
    4
];
1 + 1 == 4 / 2;

Opět platí, že lze použít i proměnné:

; namísto numerických hodnot lze použít i proměnné
(< variableA variableB)
(> variableA variableB)
(<= variableA variableB)
(>= variableA variableB)
(== variableA variableB)
(== variableA variableA)
(== (+ variableA variableA) (/ variableC variableD))
(identical? variableA variableB)

Výsledek transpřekladu do JavaScriptu:

variableA < variableB;
variableA > variableB;
variableA <= variableB;
variableA >= variableB;
variableA == variableB;
variableA == variableA;
variableA + variableA == variableC / variableD;
variableA === variableB;

8. Funkce implementující logické operátory

V JavaScriptu se používají operátory && a ||. I pro tyto operátory samozřejmě existuje ve Wispu jejich ekvivalent, ovšem zajímavé je, že někdy se && a || generují implicitně, což je příklad těchto zápisů:

; porovnání tří hodnot
; relace "menší než"
(< a b c)
 
; relace "větší než"
(> a b c)
 
; porovnání čtyř hodnot na ekvivalenci
(== 1 2 3 4)
 
; pozor! identical? je forma akceptující jen dva parametry!
;(identical? 1 2 3 4)

Výsledek transpřekladu do JavaScriptu:

a < b && b < c;
a > b && b > c;
1 == 2 && 2 == 3 && 3 == 4;

V případě potřeby se použijí formy and a or:

; operace and a or
(and true false)
(or true false)
(and a b c d)
(or a b c d)
 
; někdy je "if" zbytečné použít
(or podminka "nesplneno")
(and podminka "splneno")
 
; různé kombinace
(and (== a b) (== c d))
(and (or a b) (or c d))
 
; ještě se neztrácíte?
(and (> (+ a b) 0) (> (+ c d) y))

Výsledek transpřekladu do JavaScriptu:

true && false;
true || false;
a && b && c && d;
a || b || c || d;
podminka || 'nesplneno';
podminka && 'splneno';
 
// povšimněte si „inteligentního“ uzávorkování
a == b && c == d;
(a || b) && (c || d);
a + b > 0 && c + d > y;

9. Rozhodování založené na formě „if“ a „cond“

Další důležitou vlastností programovacího jazyka Wisp, s níž se v dnešním článku alespoň ve stručnosti seznámíme, je použití takzvaných speciálních forem. Ze syntaktického hlediska jsou speciální formy zapisovány naprosto stejným způsobem jako běžné funkce (tj. v kulatých závorkách je nejprve zapsáno jméno funkce a posléze její parametry), ovšem existuje zde jeden významný rozdíl – zatímco u funkcí jsou všechny jejich parametry nejdříve rekurzivně vyhodnoceny a teprve posléze je funkce zavolána, u speciálních forem k tomuto vyhodnocení obecně nedochází, resp. jsou vyhodnoceny pouze některé parametry (které konkrétně, to závisí na tom, o jakou speciální formu se jedná). K čemu jsou speciální formy dobré? Typickým a pro praxi naprosto nezbytným příkladem je zápis podmíněných bloků kódu. V tomto případě potřebujeme, aby se nějaká část programu vykonala pouze v případě, že je splněna (popř. nesplněna) nějaká podmínka, v opačném případě nemá být tato část programu vůbec vykonána.

Pomocí běžných funkcí by nebylo možné tuto funkcionalitu splnit, protože by se kód (předaný jako parametr – jinou možnost v Wisp ostatně prakticky nemáme) vykonal ještě před zavoláním „podmínkové“ funkce. Z toho vyplývá, že samotná podmínka, i když se syntakticky podobá volání funkce, je speciální formou. V jazyku Wisp existuje pro zápis podmíněného příkazu mj. i speciální forma if, která očekává tři parametry:

  1. podmínku (výraz=formu, která se vyhodnotí na true či false
  2. formu vyhodnocenou v případě, že je podmínka splněna
  3. formu vyhodnocenou v případě, že podmínka není splněna

Příklady použití speciální formy if:

; na základě podmínky se vyhodnotí (a vrátí jako výsledek)
; buď řetězec "mensi" nebo "vetsi"
(if (< variableA variableB) "mensi" "vetsi")
 
; větev "else" lze vynechat, vrací se nil resp. jeho ekvivalent
(if (< variableA variableB) "mensi")
 
; samotná speciální forma if může být volána uvnitř složitějšího výrazu
(* 84 (if (== a b) (+ c d) (/ e f)))
 
; vnořené formy if
(if (< n 0) "negative"
    (if (> n 0) "positive"
                "zero"))

Výsledek transpřekladu do JavaScriptu je možná překvapivý, protože se u takto jednoduchých forem nepoužije if-then-else ale funkcionálněji pojatý ternární operátor „?:“:

variableA < variableB ? 'mensi' : 'vetsi';
variableA < variableB ? 'mensi' : void 0;
 
// ternární operátor uvnitř složitějšího výrazu
84 * (a == b ? c + d : e / f);
 
// ternární operátor uvnitř jiného ternárního operátoru
n < 0 ? 'negative' : n > 0 ? 'positive' : 'zero';

Při popisu speciální formy if nesmíme zapomenout ani na makro cond, které je nedílnou součástí mnoha programovacích jazyků odvozených od LISPu. Důvod pro tuto oblibu cond (ať již se jedná v konkrétní implementaci jazyka o makro či naopak o speciální formu) je prostý: v původním návrhu programovacího jazyka LISP byl tento jazyk kromě smyčky REPL doplněn o sedm základních funkcí a dvě speciální formy: atom, car, cdr, cond, cons, eq, quote, lambda a label.

Vraťme se však k makru cond použitém i v programovacím jazyku Wisp. Toto makro akceptuje libovolný počet dvojic test-příkaz. Jednotlivé testy (jde samozřejmě o výrazy – formy) se postupně vyhodnocují a v případě, že se vyhodnotí na hodnotu true, vrátí makro cond hodnotu vyhodnoceného příkazu. V opačném případě se vyhodnotí další test atd. Pokud se ani jeden test nevyhodnotí na logickou hodnotu true, vrátí toto makro hodnotu nil:

; speciální forma cond
(cond
    (< n 0) "negative"
    (> n 0) "positive"
    :else "zero")

Výsledek transpřekladu do JavaScriptu:

n < 0 ? 'negative' : n > 0 ? 'positive' : 'else' ? 'zero' : void 0;

Závěrečný výraz void 0 odpovídá nil, řetězec „else“ se samozřejmě vyhodnotí na true.

10. Způsob zápisu funkcí

Wisp je funkcionálním jazykem a proto pro funkce, které jsou v něm vytvořeny, platí následující vlastnosti:

  1. Funkce mohou být vytvořeny kdykoli v čase běhu programu (runtime). To, jak je funkce v runtime přeložena, je již interní záležitostí daného programovacího jazyka.
  2. Funkce mohou být uloženy do proměnné (resp. přesněji řečeno navázány na jméno proměnné) či mohou být uloženy do jakékoli datové struktury (seznam funkcí, vektor funkcí atd.)
  3. Funkce mohou být předány jako parametr do jiné funkce.
  4. Funkce může být vrácena ve formě návratové hodnoty jiné funkce.
  5. Jméno sice není součástí funkce, ovšem funkce může být na jméno (symbol) navázána. Toto je pravděpodobně jedna z nejvíce matoucích vlastností funkcionálních jazyků, protože z jazyků procedurálních jsme zvyklí na to, že funkce vždy jméno, které je v naprosté většině případů známé již v době překladu. U jazyků funkcionálních (popř. jazyků hybridních) se však velmi často používají funkce beze jména, neboli funkce anonymní.

Nyjí již známe alespoň základní vlastnosti funkcí ve Wispu, jak se však funkce v tomto programovacím jazyku skutečně definují? Pro vytvoření nové bezejmenné (tj. anonymní) funkce se používá speciální forma nazvaná fn, které se v tom nejjednodušším případě předá vektor obsahující jména parametrů, za nímž je uveden seznam, jenž představuje tělo funkce (znalci LISPu patrně znají formu lambda, která má podobný význam). Samozřejmě, že v těle funkce je možné použít symbolická jména jejích parametrů a návratovou hodnotou funkce je hodnota získaná vyhodnocením těla funkce:

; anonymní funkce
(fn [x y] (+ x y))

Výsledek transpřekladu do JavaScriptu pravděpodobně není příliš překvapující (zajímavější již je, že JavaScript se skutečně může chovat jako částečně funkcionální jazyk):

(function (x, y) {
    return x + y;
});

Pro vytvoření pojmenované funkce, tj. funkce navázané na nějaký symbol (jméno), se používá makro defn. Při použití makra defn se v tom nejjednodušším případě předávají tři parametry: název nově vytvářené funkce, vektor obsahující jména parametrů funkce a konečně seznam představující tělo této funkce:

; funkce navázaná na symbol == pojmenovaná funkce
(defn add
    [x y]
    (+ x y))

Při překladu do JavaScriptu se funkce naváže hned na dvě proměnné:

var add = exports.add = function add(x, y) {
    return x + y;
};

Ve funkcích je možné používat i lokální proměnné a přidávat k funkcím metadata a dokumentační řetězce:

; lokální symboly (proměnné)
(defn add-abs
    [x y]
    (let [abs-x (if (< x 0) (- x) x)
          abs-y (if (< y 0) (- y) y)]
        (+ abs-x abs-y)))
 
; dokumentační řetězec a metadata
; (vlastnost převzaná z Clojure)
(defn inc
    "Returns a number one greater than num."
    {:version "1.0"}
    [num]
    (+ num 1))

Výsledek transpřekladu do JavaScriptu. V prvním případě se vytvoří a ihned zavolá interní anonymní funkce. Toto chování je pravděpodobně způsobeno snahou o podporu uzávěrů:

 
var addAbs = exports.addAbs = function addAbs(x, y) {
    return function () {
        var absXø¸1 = x < 0 ? 0 - x : x;
        var absYø¸1 = y < 0 ? 0 - y : y;
        return absøø1 + absøø1;
    }.call(this);
};
 
var inc = exports.inc = function inc(num) {
    return num + 1;
};

V programovacím jazyku Wisp lze vytvářet i funkce s proměnným počtem parametrů (ukážeme si příště) a s proměnnou aritou. Jen pro zajímavost se nyní podívejme na způsob, jakým se vytvoří funkce add, kterou lze volat bez parametrů, s jedním parametrem, se dvěma parametry popř. alternativně se třemi parametry. Tělo této funkce je pokaždé odlišné:

; přetěžování funkcí
(defn add
    ([]
     0)
    ([x]
     x)
    ([x y]
     (+ x y))
    ([x y z]
     (+ x y z)))

Výsledek transpřekladu do JavaScriptu je založen na použití pole arguments vytvořeného v runtime po zavolání funkce:

var add = exports.add = function add() {
    switch (arguments.length) {
    case 0:
        return 0;
    case 1:
        var x = arguments[0];
        return x;
    case 2:
        var x = arguments[0];
        var y = arguments[1];
        return x + y;
    case 3:
        var x = arguments[0];
        var y = arguments[1];
        var z = arguments[2];
        return x + y + z;
    default:
        throw RangeError('Wrong number of arguments passed');
    }
};

Na tomto příkladu jsou dobře patrné rozdíly mezi Wispem a JavaScriptem, minimálně v oblasti stručnosti zápisu a sémantiky.

11. Obsah druhé části článku

Ve druhé části tohoto článku dokončíme popis vlastností programovacího jazyka Wisp. Seznámíme se především se způsobem zápisu programových smyček s využitím loop a recur i s tím, jak je zajištěno, že se tento zápis při transpřekladu skutečně převede na programovou smyčku a nikoli na rekurzi. Posléze si ukážeme způsob vyhození popř. naopak zachycení výjimek s využitím speciálních forem try, catch, finally a raise. Taktéž se seznámíme se syntaxí zápisu volání metod a na závěr si ukážeme deklaraci a použití maker, protože právě makra mohou být jedním z důvodů, proč programovací jazyk Wisp použít v praxi namísto původního JavaScriptu nebo některých jeho „jednodušších“ nadstaveb typu Dart (které nejsou homoikonické).

12. Repositář s dnešními demonstračními příklady

Demonstrační příklady, na nichž jsme si ukazovali vlastnosti transpřekladače Wisp, byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/clojure-examples. V tabulce zobrazené pod tímto odstavcem naleznete na zdrojové kódy těchto příkladů přímé odkazy:

ict ve školství 24

Poznámka 1: zdrojové kódy s demonstračními příklady mají koncovku .clj, takže dojde ke správnému „obarvení“ při otevření těchto souborů v programátorských editorech. Transpřekladači Wisp na koncovce nezáleží, i když se v dokumentaci doporučuje použít koncovku .wisp.

Poznámka 2: soubor Makefile je možné použít pro překlad všech zdrojových souborů do JavaScriptu s využitím příkazu make. Při změně obsahu se provede překlad jen změněných souborů. Pokud potřebujete smazat soubory generované překladačem, lze použít příkaz make clean.

13. Odkazy na předchozí části tohoto seriálu

  1. Leiningen: nástroj pro správu projektů napsaných v Clojure
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure/
  2. Leiningen: nástroj pro správu projektů napsaných v Clojure (2)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-2/
  3. Leiningen: nástroj pro správu projektů napsaných v Clojure (3)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-3/
  4. Leiningen: nástroj pro správu projektů napsaných v Clojure (4)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-4/
  5. Leiningen: nástroj pro správu projektů napsaných v Clojure (5)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-5/
  6. Leiningen: nástroj pro správu projektů napsaných v Clojure (6)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-6/
  7. Programovací jazyk Clojure a databáze (1.část)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-databaze-1-cast/
  8. Pluginy pro Leiningen
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-pluginy-pro-leiningen/
  9. Programovací jazyk Clojure a knihovny pro práci s vektory a maticemi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-knihovny-pro-praci-s-vektory-a-maticemi/
  10. Programovací jazyk Clojure a knihovny pro práci s vektory a maticemi (2)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-knihovny-pro-praci-s-vektory-a-maticemi-2/
  11. Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk
    http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk/
  12. Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk (2)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk-2/
  13. Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk (dokončení)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk-dokonceni/
  14. Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure
    http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure/
  15. Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure (2)
    http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure-2/
  16. Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure (3)
    http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure-3/
  17. Programovací jazyk Clojure a práce s Gitem
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem/
  18. Programovací jazyk Clojure a práce s Gitem (2)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem-2/
  19. Programovací jazyk Clojure – triky při práci s řetězci
    http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-retezci/
  20. Programovací jazyk Clojure – triky při práci s kolekcemi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-kolekcemi/
  21. Programovací jazyk Clojure – práce s mapami a množinami
    http://www.root.cz/clanky/programovaci-jazyk-clojure-prace-s-mapami-a-mnozinami/
  22. Programovací jazyk Clojure – základy zpracování XML
    http://www.root.cz/clanky/programovaci-jazyk-clojure-zaklady-zpracovani-xml/
  23. Programovací jazyk Clojure – testování s využitím knihovny Expectations
    http://www.root.cz/clanky/programovaci-jazyk-clojure-testovani-s-vyuzitim-knihovny-expectations/
  24. Programovací jazyk Clojure – některé užitečné triky použitelné (nejenom) v testech
    http://www.root.cz/clanky/programovaci-jazyk-clojure-nektere-uzitecne-triky-pouzitelne-nejenom-v-testech/
  25. Enlive – výkonný šablonovací systém pro jazyk Clojure
    http://www.root.cz/clanky/enlive-vykonny-sablonovaci-system-pro-jazyk-clojure/
  26. Nástroj Leiningen a programovací jazyk Clojure: tvorba vlastních knihoven pro veřejný repositář Clojars
    http://www.root.cz/clanky/nastroj-leiningen-a-programovaci-jazyk-clojure-tvorba-vlastnich-knihoven-pro-verejny-repositar-clojars/

14. Odkazy na Internetu

  1. Wisp na GitHubu
    https://github.com/Gozala/wisp
  2. Wisp playground
    http://www.jeditoolkit.com/try-wisp/
  3. REPL v prohlížeči
    http://www.jeditoolkit.com/in­teractivate-wisp/
  4. Minification (programming)
    https://en.wikipedia.org/wi­ki/Minification_(programmin­g)
  5. Clojure Macro Tutorial (Part I, Getting the Compiler to Write Your Code For You)
    http://www.learningclojure­.com/2010/09/clojure-macro-tutorial-part-i-getting.html
  6. Clojure Macro Tutorial (Part II: The Compiler Strikes Back)
    http://www.learningclojure­.com/2010/09/clojure-macro-tutorial-part-ii-compiler.html
  7. Clojure Macro Tutorial (Part III: Syntax Quote)
    http://www.learningclojure­.com/2010/09/clojure-macro-tutorial-part-ii-syntax.html
  8. Tech behind Tech: Clojure Macros Simplified
    http://techbehindtech.com/2010/09/28/clo­jure-macros-simplified/
  9. Fatvat – Exploring functional programming: Clojure Macros
    http://www.fatvat.co.uk/2009/02/clo­jure-macros.html
  10. Eulerovo ??slo
    http://cs.wikipedia.org/wi­ki/Eulerovo_??slo
  11. List comprehension
    http://en.wikipedia.org/wi­ki/List_comprehension
  12. List Comprehensions in Clojure
    http://asymmetrical-view.com/2008/11/18/list-comprehensions-in-clojure.html
  13. Clojure Programming Concepts: List Comprehension
    http://en.wikibooks.org/wi­ki/Clojure_Programming/Con­cepts#List_Comprehension
  14. Clojure core API: for macro
    http://clojure.github.com/clo­jure/clojure.core-api.html#clojure.core/for
  15. cirrus machina – The Clojure for macro
    http://www.cirrusmachina.com/blog/com­ment/the-clojure-for-macro/
  16. Clojure.org: Clojure home page
    http://clojure.org/downloads
  17. Clojure.org: Vars and the Global Environment
    http://clojure.org/Vars
  18. Clojure.org: Refs and Transactions
    http://clojure.org/Refs
  19. Clojure.org: Atoms
    http://clojure.org/Atoms
  20. Clojure.org: Agents as Asynchronous Actions
    http://clojure.org/agents
  21. A Couple of Clojure Agent Examples
    http://lethain.com/a-couple-of-clojure-agent-examples/
  22. Clojure – Functional Programming for the JVM
    http://java.ociweb.com/mar­k/clojure/article.html
  23. Clojure quick reference
    http://faustus.webatu.com/clj-quick-ref.html
  24. 4Clojure
    http://www.4clojure.com/
  25. ClojureDoc
    http://clojuredocs.org/
  26. Clojure (Wikipedia EN)
    http://en.wikipedia.org/wiki/Clojure
  27. Clojure (Wikipedia CS)
    http://cs.wikipedia.org/wiki/Clojure
  28. Riastradh's Lisp Style Rules
    http://mumble.net/~campbe­ll/scheme/style.txt
  29. Dynamic Languages Strike Back
    http://steve-yegge.blogspot.cz/2008/05/dynamic-languages-strike-back.html
  30. Scripting: Higher Level Programming for the 21st Century
    http://www.tcl.tk/doc/scripting.html
  31. Java Virtual Machine Support for Non-Java Languages
    http://docs.oracle.com/ja­vase/7/docs/technotes/gui­des/vm/multiple-language-support.html
  32. The Lua VM, on the Web
    https://kripken.github.io/lu­a.vm.js/lua.vm.js.html
  33. Lua.vm.js REPL
    https://kripken.github.io/lu­a.vm.js/repl.html
  34. lua2js
    https://www.npmjs.com/package/lua2js
  35. lua2js na GitHubu
    https://github.com/basicer/lua2js-dist
  36. Seriál o programovacím jazyku Lua
    http://www.root.cz/serialy/pro­gramovaci-jazyk-lua/
  37. Source-to-source compiler
    https://en.wikipedia.org/wiki/Source-to-source_compiler
  38. JavaScript is Assembly Language for the Web: Sematic Markup is Dead! Clean vs. Machine-coded HTML
    http://www.hanselman.com/blog/Ja­vaScriptIsAssemblyLanguage­ForTheWebSematicMarkupIsDe­adCleanVsMachinecodedHTML­.aspx
  39. JavaScript is Web Assembly Language and that's OK.
    http://www.hanselman.com/blog/Ja­vaScriptIsWebAssemblyLangu­ageAndThatsOK.aspx
  40. Dart
    https://www.dartlang.org/
  41. CoffeeScript
    http://coffeescript.org/
  42. TypeScript
    http://www.typescriptlang.org/
  43. Lua (programming language)
    http://en.wikipedia.org/wi­ki/Lua_(programming_langu­age)
  44. Static single assignment form (SSA)
    http://en.wikipedia.org/wi­ki/Static_single_assignmen­t_form
  45. LuaJIT 2.0 SSA IRhttp://wiki.luajit.org/SSA-IR-2.0
  46. The LuaJIT Project
    http://luajit.org/index.html
  47. LuaJIT FAQ
    http://luajit.org/faq.html
  48. LuaJIT Performance Comparison
    http://luajit.org/performance.html
  49. LuaJIT 2.0 intellectual property disclosure and research opportunities
    http://article.gmane.org/gma­ne.comp.lang.lua.general/58908
  50. LuaJIT Wiki
    http://wiki.luajit.org/Home
  51. LuaJIT 2.0 Bytecode Instructions
    http://wiki.luajit.org/Bytecode-2.0
  52. Programming in Lua (first edition)
    http://www.lua.org/pil/contents.html
  53. Lua 5.2 sources
    http://www.lua.org/source/5.2/
  54. Tcl Plugin Version 3
    http://www.tcl.tk/software/plugin/
  55. JavaScript: The Web Assembly Language?
    http://www.informit.com/ar­ticles/article.aspx?p=1856657
  56. asm.js
    http://asmjs.org/
  57. List of languages that compile to JS
    https://github.com/jashke­nas/coffeescript/wiki/List-of-languages-that-compile-to-JS
  58. REPL
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  59. The LLVM Compiler Infrastructure
    http://llvm.org/ProjectsWithLLVM/
  60. clang: a C language family frontend for LLVM
    http://clang.llvm.org/
  61. emscripten
    http://kripken.github.io/emscripten-site/
  62. LLVM Backend („Fastcomp“)
    http://kripken.github.io/emscripten-site/docs/building_from_source/LLVM-Backend.html#llvm-backend
  63. Emscripten – Fastcomp na GitHubu
    https://github.com/kripken/emscripten-fastcomp
  64. Clang (pro Emscripten) na GitHubu
    https://github.com/kripken/emscripten-fastcomp-clang
  65. Why not use JavaScript?
    https://ckknight.github.i­o/gorillascript/

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.