Obsah
1. Propojení světa LISPu se světem JavaScriptu s využitím transpřekladače Wisp
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“
12. Repositář s dnešními demonstračními příklady
13. Odkazy na předchozí části tohoto seriálu
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:
- literály (numerické hodnoty, pravdivostní hodnoty, znaky, řetězce; vyhodnocují se samy na sebe)
- symboly a takzvaná „klíčová hesla“ (keywords)
- složené formy (seznamy, vektory, množiny a mapy, seznamy mají navíc zvláštní postavení kvůli způsobu svého vyhodnocení)
- 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:
- podmínku (výraz=formu, která se vyhodnotí na true či false
- formu vyhodnocenou v případě, že je podmínka splněna
- 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:
- 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.
- 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.)
- Funkce mohou být předány jako parametr do jiné funkce.
- Funkce může být vrácena ve formě návratové hodnoty jiné funkce.
- 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 má 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:
# | Příklad/skriptu | Github |
---|---|---|
1 | 1_data_types.clj | https://github.com/tisnik/clojure-examples/blob/master/wisp/1_data_types.clj |
2 | 2_collections.clj | https://github.com/tisnik/clojure-examples/blob/master/wisp/2_collections.clj |
3 | 3_arithmetic_functions.clj | https://github.com/tisnik/clojure-examples/blob/master/wisp/3_arithmetic_functions.clj |
4 | 4_relop.clj | https://github.com/tisnik/clojure-examples/blob/master/wisp/4_relop.clj |
5 | 5_boolean_op.clj | https://github.com/tisnik/clojure-examples/blob/master/wisp/5_boolean_op.clj |
6 | 6_conditions.clj | https://github.com/tisnik/clojure-examples/blob/master/wisp/6_conditions.clj |
7 | 7_functions.clj | https://github.com/tisnik/clojure-examples/blob/master/wisp/7_functions.clj |
9 | Makefile | https://github.com/tisnik/clojure-examples/blob/master/wisp/Makefile |
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
- Leiningen: nástroj pro správu projektů napsaných v Clojure
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure/ - 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/ - 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/ - 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/ - 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/ - 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/ - Programovací jazyk Clojure a databáze (1.část)
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-databaze-1-cast/ - Pluginy pro Leiningen
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-pluginy-pro-leiningen/ - 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/ - 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/ - 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/ - 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/ - 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/ - Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure
http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure/ - 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/ - 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/ - Programovací jazyk Clojure a práce s Gitem
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem/ - Programovací jazyk Clojure a práce s Gitem (2)
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem-2/ - Programovací jazyk Clojure – triky při práci s řetězci
http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-retezci/ - Programovací jazyk Clojure – triky při práci s kolekcemi
http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-kolekcemi/ - Programovací jazyk Clojure – práce s mapami a množinami
http://www.root.cz/clanky/programovaci-jazyk-clojure-prace-s-mapami-a-mnozinami/ - Programovací jazyk Clojure – základy zpracování XML
http://www.root.cz/clanky/programovaci-jazyk-clojure-zaklady-zpracovani-xml/ - Programovací jazyk Clojure – testování s využitím knihovny Expectations
http://www.root.cz/clanky/programovaci-jazyk-clojure-testovani-s-vyuzitim-knihovny-expectations/ - 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/ - Enlive – výkonný šablonovací systém pro jazyk Clojure
http://www.root.cz/clanky/enlive-vykonny-sablonovaci-system-pro-jazyk-clojure/ - 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
- Wisp na GitHubu
https://github.com/Gozala/wisp - Wisp playground
http://www.jeditoolkit.com/try-wisp/ - REPL v prohlížeči
http://www.jeditoolkit.com/interactivate-wisp/ - Minification (programming)
https://en.wikipedia.org/wiki/Minification_(programming) - 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 - Clojure Macro Tutorial (Part II: The Compiler Strikes Back)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-ii-compiler.html - Clojure Macro Tutorial (Part III: Syntax Quote)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-ii-syntax.html - Tech behind Tech: Clojure Macros Simplified
http://techbehindtech.com/2010/09/28/clojure-macros-simplified/ - Fatvat – Exploring functional programming: Clojure Macros
http://www.fatvat.co.uk/2009/02/clojure-macros.html - Eulerovo ??slo
http://cs.wikipedia.org/wiki/Eulerovo_??slo - List comprehension
http://en.wikipedia.org/wiki/List_comprehension - List Comprehensions in Clojure
http://asymmetrical-view.com/2008/11/18/list-comprehensions-in-clojure.html - Clojure Programming Concepts: List Comprehension
http://en.wikibooks.org/wiki/Clojure_Programming/Concepts#List_Comprehension - Clojure core API: for macro
http://clojure.github.com/clojure/clojure.core-api.html#clojure.core/for - cirrus machina – The Clojure for macro
http://www.cirrusmachina.com/blog/comment/the-clojure-for-macro/ - Clojure.org: Clojure home page
http://clojure.org/downloads - Clojure.org: Vars and the Global Environment
http://clojure.org/Vars - Clojure.org: Refs and Transactions
http://clojure.org/Refs - Clojure.org: Atoms
http://clojure.org/Atoms - Clojure.org: Agents as Asynchronous Actions
http://clojure.org/agents - A Couple of Clojure Agent Examples
http://lethain.com/a-couple-of-clojure-agent-examples/ - Clojure – Functional Programming for the JVM
http://java.ociweb.com/mark/clojure/article.html - Clojure quick reference
http://faustus.webatu.com/clj-quick-ref.html - 4Clojure
http://www.4clojure.com/ - ClojureDoc
http://clojuredocs.org/ - Clojure (Wikipedia EN)
http://en.wikipedia.org/wiki/Clojure - Clojure (Wikipedia CS)
http://cs.wikipedia.org/wiki/Clojure - Riastradh's Lisp Style Rules
http://mumble.net/~campbell/scheme/style.txt - Dynamic Languages Strike Back
http://steve-yegge.blogspot.cz/2008/05/dynamic-languages-strike-back.html - Scripting: Higher Level Programming for the 21st Century
http://www.tcl.tk/doc/scripting.html - Java Virtual Machine Support for Non-Java Languages
http://docs.oracle.com/javase/7/docs/technotes/guides/vm/multiple-language-support.html - The Lua VM, on the Web
https://kripken.github.io/lua.vm.js/lua.vm.js.html - Lua.vm.js REPL
https://kripken.github.io/lua.vm.js/repl.html - lua2js
https://www.npmjs.com/package/lua2js - lua2js na GitHubu
https://github.com/basicer/lua2js-dist - Seriál o programovacím jazyku Lua
http://www.root.cz/serialy/programovaci-jazyk-lua/ - Source-to-source compiler
https://en.wikipedia.org/wiki/Source-to-source_compiler - JavaScript is Assembly Language for the Web: Sematic Markup is Dead! Clean vs. Machine-coded HTML
http://www.hanselman.com/blog/JavaScriptIsAssemblyLanguageForTheWebSematicMarkupIsDeadCleanVsMachinecodedHTML.aspx - JavaScript is Web Assembly Language and that's OK.
http://www.hanselman.com/blog/JavaScriptIsWebAssemblyLanguageAndThatsOK.aspx - Dart
https://www.dartlang.org/ - CoffeeScript
http://coffeescript.org/ - TypeScript
http://www.typescriptlang.org/ - Lua (programming language)
http://en.wikipedia.org/wiki/Lua_(programming_language) - Static single assignment form (SSA)
http://en.wikipedia.org/wiki/Static_single_assignment_form - LuaJIT 2.0 SSA IRhttp://wiki.luajit.org/SSA-IR-2.0
- The LuaJIT Project
http://luajit.org/index.html - LuaJIT FAQ
http://luajit.org/faq.html - LuaJIT Performance Comparison
http://luajit.org/performance.html - LuaJIT 2.0 intellectual property disclosure and research opportunities
http://article.gmane.org/gmane.comp.lang.lua.general/58908 - LuaJIT Wiki
http://wiki.luajit.org/Home - LuaJIT 2.0 Bytecode Instructions
http://wiki.luajit.org/Bytecode-2.0 - Programming in Lua (first edition)
http://www.lua.org/pil/contents.html - Lua 5.2 sources
http://www.lua.org/source/5.2/ - Tcl Plugin Version 3
http://www.tcl.tk/software/plugin/ - JavaScript: The Web Assembly Language?
http://www.informit.com/articles/article.aspx?p=1856657 - asm.js
http://asmjs.org/ - List of languages that compile to JS
https://github.com/jashkenas/coffeescript/wiki/List-of-languages-that-compile-to-JS - REPL
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - The LLVM Compiler Infrastructure
http://llvm.org/ProjectsWithLLVM/ - clang: a C language family frontend for LLVM
http://clang.llvm.org/ - emscripten
http://kripken.github.io/emscripten-site/ - LLVM Backend („Fastcomp“)
http://kripken.github.io/emscripten-site/docs/building_from_source/LLVM-Backend.html#llvm-backend - Emscripten – Fastcomp na GitHubu
https://github.com/kripken/emscripten-fastcomp - Clang (pro Emscripten) na GitHubu
https://github.com/kripken/emscripten-fastcomp-clang - Why not use JavaScript?
https://ckknight.github.io/gorillascript/