Obsah
1. Projekt Mori aneb perzistentní datové struktury pro JavaScript
2. Základní vlastnosti datových struktur v knihovně Mori
3. Neměnnost (immutability) datových struktur
4. Perzistence datových struktur
5. První demonstrační příklad: použití knihovny Mori na webové stránce
7. Druhý demonstrační příklad: použití seznamů a vektorů
9. Základní funkce pro práci se sekvencemi
10. Třetí demonstrační příklad: použití funkcí first a rest
12. Čtvrtý demonstrační příklad: použití funkcí range, take a map
13. Repositář se zdrojovými kódy všech dnešních demonstračních příkladů
1. Projekt Mori aneb perzistentní datové struktury pro JavaScript
V několika posledních letech se vývojáři používající programovací jazyk JavaScript mohli setkat se dvěma zajímavými fenomény. Prvním fenoménem je postupné rozšiřování použití tohoto (nutno říci, že mnohdy neprávem podceňovaného) jazyka z webových browserů i do dalších aplikačních oblastí, zejména na servery a taktéž, i když v poněkud menší míře, na desktopy. Využití JavaScriptu na serverech sice ve skutečnosti není žádný nový či přelomový objev, protože podobnou funkci již kdysi dávno (konkrétně od roku 1994) nabízel komerční webový server nazvaný Netscape Enterprise Server (jednalo se o technologii pojmenovanou Server-Side JavaScript aneb SSJS), ovšem teprve s rozvojem známého a populárního projektu Node.js je možné mluvit o masivní adaptaci JavaScriptu i na serverech.
Na desktopu se JavaScript používá například v desktopovém prostředí GNOME (Gjs) či ve Windows Store, zajímavé je ovšem i jeho využití například v textovém editoru Atom (který je vnitřně rozdělen na klientskou a serverovou část, zjednodušeně lze říci, že klientská část je kombinací JavaScriptu a browseru). Navíc se objevují i „šílenosti“ typu OS.js, které ukazují možnosti tohoto jazyka i vyspělost jeho interpretrů.
Druhým fenoménem, s nímž se již mohlo setkat poměrně velké množství vývojářů, je vznik mnoha rozmanitých transpřekladačů (transcompilers, source-to-source compilers), jejichž cílovým jazykem je právě JavaScript. 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 do bajtkódu, protože to už je role běžných překladačů). V současnosti existuje již poměrně velké množství transpřekladačů do JavaScriptu, nejpopulárnější jsou pak transpřekladače pro jazyky CoffeeScript, ClojureScript, TypeScript, Dart a Haxe. Nesmíme ovšem zapomenout ani na jazyk Wisp, s nímž jsme se již na stránkách Roota potkali [1] [2].
A právě v souvislosti s CoffeeScriptem, ClojureScriptem a v neposlední řadě i Wispem se setkáme s potřebou použití perzistentních (neměnných) datových struktur. Jednou z knihoven, která perzistentní datové struktury programátorům používajícím (nejenom) JavaScript nabízí, je knihovna Mori.
2. Základní vlastnosti datových struktur v knihovně Mori
Interně je knihovna Mori naprogramována poměrně neobvyklým způsobem, protože všechny perzistentní datové struktury, které jsou v ní implementovány, ve skutečnosti pochází z ClojureScriptu, jehož starší verzi jsme si již na stránkách Rootu popsali (nastal již ovšem ten pravý čas si někdy představit i novou verzi tohoto jazyka). Knihovna Mori tedy umožňuje, aby i programátoři používající „pouhý“ JavaScript (či TypeScript popř. CoffeeScript) měli přístup k optimalizovaným algoritmům vyvinutých v rámci projektu ClojureScript, aniž by se přitom bylo nutné učit odlišný přístup k tvorbě programů, který je v Clojure a ClojureScriptu vyžadován. Nicméně se vraťme k již zmíněným perzistentním datovým strukturám, které jsou v knihovně Mori uživatelům dostupné. Jedná se o neměnné seznamy (list), vektory (vector), množiny (set), mapy (map) a taktéž fronty (queue):
# | Datová struktura | Konstruktor | Alt. implementace |
---|---|---|---|
1 | seznam | mori.list(prvky) | × |
2 | vektor | mori.vector(prvky) | × |
3 | množina | mori.set(prvky) | mori.sortedSet(prvky) |
4 | mapa | mori.hashMap(klíče+hodnoty) | mori.sorted_map(klíče+hodnoty) |
5 | fronta | mori.queue(prvky) | × |
Povšimněte si, že mapy a množiny mohou být implementovány dvěma odlišnými způsoby, v závislosti na tom, zda mají být jejich prvky setříděny či nikoli.
3. Neměnnost (immutability) datových struktur
Všech pět typů datových struktur (seznamů, vektorů, množin, map a front) má několik důležitých společných vlastností. Základní vlastností společnou všem pěti typům datových struktur je jejich neměnnost (immutability). To znamená, že již ve chvíli, kdy je některá datová struktura vytvořena, je po celou další dobu její existence v běžícím programu určen její obsah, tj. hodnoty všech prvků struktury. Na první pohled to sice možná může vypadat zvláštně, ale i s takto se chovajícími strukturami je možné v reálným programech pracovat a to dokonce velmi efektivním způsobem (navíc se i zjednodušuje testování aplikace). Ostatně i v samotném JavaScriptu jsou některé hodnoty a objekty neměnné. Pravděpodobně nejviditelnějším příkladem jsou řetězce a samozřejmě taktéž všechny hodnoty primitivního datového typu (číslo, pravdivostní hodnota, …).
4. Perzistence datových struktur
Kromě neměnnosti (immutability) je další společnou vlastností všech čtyř typů kolekcí jejich persistence. Většina standardních funkcí poskytovaných knihovnou Mori se totiž snaží o to, aby jednou vytvořené sekvence (dejme tomu pro jednoduchost seznam) mohly být znovupoužity i v případě, že je vytvořen nový seznam, který v sobě obsahuje i seznam starší. Ten stále existuje a mohou na něj existovat reference používané například i v jiných paralelně běžících workerech či ve vláknech (pokud interpret JavaScriptu podporuje tvorbu většího množství vláken).
Vzhledem k tomu, že se obsah starého seznamu nemůže změnit (seznam je neměnitelný), může například funkce conj (což je obdoba funkce cons známé již z LISPu) jednoduše k seznamu přidat nový první prvek (head) s tím, že tento prvek ukazuje na původní seznam – jinými slovy není nutné, alespoň v tomto případě, vytvářet kopii (ať již plytkou či hlubokou) původního seznamu, což přispívá k tomu, že mnohé operace nad kolekcemi jsou ve skutečnosti velmi rychlé, i když by se podle jejich popisu mohlo zdát, že jejich implementace vyžaduje provedení časově složitých operací. Je pouze důležité si zvolit správnou datovou strukturu, což se v praxi týká rozhodování mezi použitím seznamů, vektorů či front.
5. První demonstrační příklad: použití knihovny Mori na webové stránce
Všechny datové struktury poskytované knihovnou Mori jsou implementovány formou objektů, tj. nelze s nimi zacházet jako s polem atd. – veškeré manipulace se provádí přes k tomu určené funkce. Ovšem Mori umožňuje převádět „své“ datové struktury na struktury zpracovávané JavaScriptem, což může být velmi užitečné ve chvíli, kdy se tato knihovna začíná používat v již rozpracovaném projektu. V dnešním prvním demonstračním příkladu je ukázáno, jak se vytvoří perzistentní seznam s využitím konstruktoru mori.list(), jak se následně zjistí počet prvků tohoto seznamu a taktéž způsob převodu perzistentního seznamu pomocí funkce mori.toJs() na běžné JavaScriptové pole:
// ------------------------------------------------------------ // Knihovna Mori: demonstrační příklad číslo 1 // ------------------------------------------------------------ // vytvoření perzistentního seznamu var moriList = mori.list(1,2,3); // výpočet délky perzistentního seznamu var listSize = mori.count(moriList); // převod perzistentního seznamu na "normální" JavaScriptové pole var jsList = mori.toJs(moriList); // výpis základních informaci o perzistentním seznamu i o jeho // JavaScriptové variantě console.log("List size: " + listSize); console.log("List to JavaScript: " + jsList); // ------------------------------------------------------------ // Výpis typu objektu moriList a jsList - první varianta // ------------------------------------------------------------ console.log("Using typeof:"); // operátor typeof nám moc nepomůže v případě objektových typu console.log("jsList type: " + typeof jsList); console.log("moriList type: " + typeof moriList); // ------------------------------------------------------------ // Výpis typu objektu moriList a jsList - druha varianta // ------------------------------------------------------------ console.log("Using Object.toString():"); // získáme referenci na toString a uložíme do proměnné toClass var toClass = {}.toString // Výpis informace o typech objektu console.log("jsList type: " + toClass.call(jsList)); console.log("moriList type: " + toClass.call(moriList)); // ------------------------------------------------------------ // Finito // ------------------------------------------------------------
Obrázek 1: Výpis na konzoli zachycený ve Firebugu.
Příklad je napsán takovým způsobem, aby ho bylo možné použít na webové stránce; konzolový výstup bude čitelný například přes Firebug ve Firefoxu popř. s využitím nástroje Inspect Page v prohlížeči Midori (ovšem i další webové prohlížeče používají podobné ladicí nástroje). Samotná webová stránka je pojata skutečně minimalisticky, protože vyžaduje pouze načtení dvou skriptů – samotné knihovny Mori a zdrojového kódu prvního demonstračního příkladu. Zdrojový kód mori.js získáte instalací knihovny příkazem npm install mori (soubor bude umístěn v adresáři ~/node_modules/mori), zdrojový kód uložený v souboru mori01.js byl vypsán před tímto odstavcem:
<!doctype html> <html> <head> <title>Mori tests #01</title> <meta charset="utf-8"> <script src="mori.js"></script> <script src="mori_01.js"></script> </head> <body> <h1>Mori tests #01</h1> </body> </html>
V ladicí konzoli webového prohlížeče by se po otevření výše uvedené testovací stránky měly objevit tyto řádky:
List size: 3 List to JavaScript: 1,2,3 Using typeof: jsList type: object moriList type: object Using Object.toString(): jsList type: [object Array] moriList type: [object Object]
Povšimněte si, že moriList je z pohledu JavaScriptu „nějakým“ obecným objektem, zatímco jsList je plnohodnotné JavaScriptové pole (jeho prvky ovšem již s původním seznamem nemusí sdílet stejný paměťový prostor!).
Obrázek 2: Výpis na konzoli zachycený v nástroji Inspect Page (Midori).
6. Seznamy a vektory
Způsob vytváření seznamů a vektorů s využitím konstruktorů mori.list již známe. Podobně se tvoří vektory konstruktorem mori.vector. Kromě toho knihovna Mori obsahuje i množství funkcí, které buď vrací stav dané datové struktury, vrací jeden vybraný prvek z této struktury, nebo vrací novou strukturu s přidaným či ubraným prvkem. Jedná se o následující funkce (povšimněte si, že zde není použit klasický OOP přístup, což je ale v tomto případě spíše výhoda; podrobnosti si řekneme později):
# | Funkce | Význam funkce |
---|---|---|
1 | mori.count | vrátí počet prvků v seznamu či vektoru |
2 | mori.isEmpty | vrátí true v případě, že je datová struktura prázdná |
3 | mori.empty | vrátí prázdnou datovou strukturu stejného typu |
4 | mori.distinct | vrací novou datovou strukturu bez duplicitních prvků (velmi užitečné) |
5 | mori.conj | vrátí novou datovou strukturu s přidaným prvkem |
6 | mori.pop | vrátí kolekci bez prvního prvku (seznamy) nebo bez prvku posledního (vektory) |
7 | mori.peek | vrátí první prvek (seznamy), popř. poslední prvek (vektory) |
8 | mori.first | vrátí první prvek kolekce * |
9 | mori.last | vrátí poslední prvek kolekce |
10 | mori.nth | získání n-tého prvku kolekce (u seznamů má lineární složitost!) |
11 | mori.get | vrátí n-tý prvek seznamu či vektoru (má obecnější význam než nth) |
Poznámka: funkce mori.first() ve skutečnosti není omezena pouze na seznamy a vektory, protože ji je možné použít i na obecné sekvence a dokonce i na lazy sekvence popsané v dalších kapitolách.
Některé funkce z výše uvedené tabulky se chovají stejně pro seznamy i pro vektory, další funkce jsou však odlišné v chápání, který „konec“ datové struktury je modifikován, či z kterého „konce“ se mají vracet prvky:
# | Funkce | Seznam | Vektor |
---|---|---|---|
1 | mori.conj | nové prvky + původní seznam | vektor + nové prvky |
2 | mori.pop | seznam bez prvního prvku | vektor bez posledního prvku |
3 | mori.peek | první prvek seznamu | poslední prvek vektoru |
4 | mori.first | první prvek seznamu | první prvek vektoru |
5 | mori.last | poslední prvek seznamu | poslední prvek vektoru |
Důležité je při optimalizaci aplikací i porovnání výpočetní složitosti některých funkcí z předchozí tabulky, protože právě odlišná složitost může výrazným způsobem ovlivnit výkonnost celé aplikace, zejména tehdy, pokud se budou používat kolekce s velkým množstvím prvků. Zajímavé je, že funkce count má v obou případech konstantní složitost: O(1). To znamená, že v knihovně Mori nemá smysl si dopředu počítat a pamatovat počet prvků seznamu. Naproti tomu funkce nth se sice u obou typů kolekcí chová stejně, má však výrazně odlišnou složitost: O(n) v případě seznamů (lineární složitost) a O(log32N) v případě vektorů (logaritmická složitost). U krátkých vektorů (do 32 prvků) je složitost konstantní a i u delších vektorů je počet kroků nutných pro získání n-tého prvku velmi malý (například dva kroky pro vektory o délce přibližně 1000 prvků atd., maximálně se jedná o sedm kroků). Složitost funkce peek je v případě vektoru taktéž rovna O(log32N), na rozdíl od funkce last se složitostí O(N) – v případě vektorů tedy vždy používejte peek!
# | Funkce | Seznam | Vektor |
---|---|---|---|
1 | mori.count | O(1) | O(1) |
2 | mori.nth | O(N) | O(log32N) |
3 | mori.pop | O(1) | O(log32N) |
4 | mori.peek | O(1) | O(log32N) |
5 | mori.first | O(1) | O(1) |
6 | mori.last | O(N) | O(N) |
Povšimněte si, že vektory ve skutečnosti neodpovídají složitostí některých funkcí běžně chápaným vektorům-jednorozměrným polím. Je tomu tak z toho důvodu, že v knihovně Mori jsou vektory implementovány odlišným způsobem a to zejména proto, aby bylo možné jednoduše implementovat funkci conj, tj. aby se již vytvořená datová struktura mohla sdílet mezi větším množstvím vektorů (to je možné i díky jejich neměnnosti).
7. Druhý demonstrační příklad: použití seznamů a vektorů
Ve druhém demonstračním příkladu jsou ukázány základní vlastnosti některých výše popsaných funkcí, zejména chování ve chvíli, kdy je datová struktura (seznam či vektor) prázdná. Nejprve se podívejme na zdrojový kód příkladu, který je hodně „ukecaný“, a to kvůli přehlednosti:
// ------------------------------------------------------------ // Knihovna Mori: demonstrační příklad číslo 2 // ------------------------------------------------------------ // vytvoření čtyř seznamu různé délky var list1 = mori.list(1,2,3); var list2 = mori.list(1,2); var list3 = mori.list(1); var list4 = mori.list(); // vytvoření čtyř vektoru různé délky var vector1 = mori.vector(1,2,3); var vector2 = mori.vector(1,2); var vector3 = mori.vector(1); var vector4 = mori.vector(); // ------------------------------------------------------------ // Výpis informaci o délkách seznamu a vektoru // ------------------------------------------------------------ console.log("List1 size: " + mori.count(list1)); console.log("List2 size: " + mori.count(list2)); console.log("List3 size: " + mori.count(list3)); console.log("List4 size: " + mori.count(list4)); console.log("Vector1 size: " + mori.count(vector1)); console.log("Vector2 size: " + mori.count(vector2)); console.log("Vector3 size: " + mori.count(vector3)); console.log("Vector4 size: " + mori.count(vector4)); // ------------------------------------------------------------ // Vyzkoušení funkce conj // ------------------------------------------------------------ console.log("Using conj:"); console.log("conj list1: " + mori.conj(list1, 42)); console.log("conj list2: " + mori.conj(list2, 42)); console.log("conj list3: " + mori.conj(list3, 42)); console.log("conj list4: " + mori.conj(list4, 42)); console.log("conj vector1: " + mori.conj(vector1, 42)); console.log("conj vector2: " + mori.conj(vector2, 42)); console.log("conj vector3: " + mori.conj(vector3, 42)); console.log("conj vector4: " + mori.conj(vector4, 42)); // ------------------------------------------------------------ // Vyzkoušení funkce pop // ------------------------------------------------------------ console.log("Using pop:"); console.log("pop list1: " + mori.pop(list1)); console.log("pop list2: " + mori.pop(list2)); console.log("pop list3: " + mori.pop(list3)); // následující volání skončí chybou! //console.log("pop list4: " + mori.pop(list4)); console.log("pop vector1: " + mori.pop(vector1)); console.log("pop vector2: " + mori.pop(vector2)); console.log("pop vector3: " + mori.pop(vector3)); // následující volání skončí chybou! //console.log("pop vector4: " + mori.pop(vector4)); // ------------------------------------------------------------ // Vyzkoušení funkce peek // ------------------------------------------------------------ console.log("Using peek:"); console.log("peek list1: " + mori.peek(list1)); console.log("peek list2: " + mori.peek(list2)); console.log("peek list3: " + mori.peek(list3)); console.log("peek list4: " + mori.peek(list4)); console.log("peek vector1: " + mori.peek(vector1)); console.log("peek vector2: " + mori.peek(vector2)); console.log("peek vector3: " + mori.peek(vector3)); console.log("peek vector4: " + mori.peek(vector4)); // ------------------------------------------------------------ // Vyzkoušení funkce first // ------------------------------------------------------------ console.log("Using first:"); console.log("first list1: " + mori.first(list1)); console.log("first list2: " + mori.first(list2)); console.log("first list3: " + mori.first(list3)); console.log("first list4: " + mori.first(list4)); console.log("first vector1: " + mori.first(vector1)); console.log("first vector2: " + mori.first(vector2)); console.log("first vector3: " + mori.first(vector3)); console.log("first vector4: " + mori.first(vector4)); // ------------------------------------------------------------ // Vyzkoušení funkce last // ------------------------------------------------------------ console.log("Using last:"); console.log("last list1: " + mori.last(list1)); console.log("last list2: " + mori.last(list2)); console.log("last list3: " + mori.last(list3)); console.log("last list4: " + mori.last(list4)); console.log("last vector1: " + mori.last(vector1)); console.log("last vector2: " + mori.last(vector2)); console.log("last vector3: " + mori.last(vector3)); console.log("last vector4: " + mori.last(vector4)); // ------------------------------------------------------------ // Vyzkoušení funkce distinct // ------------------------------------------------------------ console.log("Using distinct:"); console.log("distinct []: " + mori.distinct(mori.list())); console.log("distinct [1]: " + mori.distinct(mori.list(1))); console.log("distinct [1 2]: " + mori.distinct(mori.list(1,2))); console.log("distinct [1 2 1]: " + mori.distinct(mori.list(1,2,1))); console.log("distinct [1 2 1 2]: " + mori.distinct(mori.list(1,2,1,2))); // ------------------------------------------------------------ // Finito // ------------------------------------------------------------
Zdrojový kód HTML stránky použité pro spuštění druhého demonstračního příkladu v prohlížeči:
<!doctype html> <html> <head> <title>Mori tests #02</title> <meta charset="utf-8"> <script src="mori.js"></script> <script src="mori_02.js"></script> </head> <body> <h1>Mori tests #02</h1> </body> </html>
Příklad by měl po svém spuštění na konzoli (webového prohlížeče) vypsat tyto zprávy:
List1 size: 3 List2 size: 2 List3 size: 1 List4 size: 0 Vector1 size: 3 Vector2 size: 2 Vector3 size: 1 Vector4 size: 0 Using conj: conj list1: (42 1 2 3) conj list2: (42 1 2) conj list3: (42 1) conj list4: (42) conj vector1: [1 2 3 42] conj vector2: [1 2 42] conj vector3: [1 42] conj vector4: [42] Using pop: pop list1: (2 3) pop list2: (2) pop list3: () pop vector1: [1 2] pop vector2: [1] pop vector3: [] Using peek: peek list1: 1 peek list2: 1 peek list3: 1 peek list4: null peek vector1: 3 peek vector2: 2 peek vector3: 1 peek vector4: null Using first: first list1: 1 first list2: 1 first list3: 1 first list4: null first vector1: 1 first vector2: 1 first vector3: 1 first vector4: null Using last: last list1: 3 last list2: 2 last list3: 1 last list4: null last vector1: 3 last vector2: 2 last vector3: 1 last vector4: null Using distinct: distinct []: () distinct [1]: (1) distinct [1 2]: (1 2) distinct [1 2 1]: (1 2) distinct [1 2 1 2]: (1 2)
Obrázek 3: Výpis na konzoli zachycený v nástroji Inspect Page (Midori).
Povšimněte si chování funkce mori.pop, která skončí s chybou ve chvíli, kdy se jí předá prázdná datová struktura, ať již se jedná o seznam či o vektor:
Obrázek 4: Funkce mori.pop volaná pro prázdný seznam.
Obrázek 5: Funkce mori.pop volaná pro prázdný vektor.
8. Sekvence a lazy sekvence
Na všechny datové typy, které jsou v knihovně Mori programátorům nabízeny, se můžeme dívat jako na různým způsobem implementované datové kolekce (collections), které je možné velmi snadno převést na takzvané sekvence, a to konkrétně s využitím funkce seq. Mnohdy se dokonce konverze datové kolekce na sekvenci provede automaticky uvnitř jiné funkce (s vlastními daty se přitom většinou žádným způsobem nemanipuluje, konverze znamená, že se pouze použije obecnější pohled na data). To ale současně znamená, že všechny datové typy (tj. sekvence) mají společné rozhraní, které svými základními možnostmi zhruba odpovídá iterátorům známým například z programovacího jazyka Java (ve skutečnosti sice jazyk JavaScript pojem „rozhraní“ (interface) nepoužívá ve stejném smyslu jako jazyk Java, ovšem funkce pro práci se sekvencemi lze skutečně použít například jak pro seznamy, tak i pro vektory, mapy atd., bez ohledu na to, jaká datová struktura je pro implementaci sekvence použita).
V knihovně Mori existuje pro práci se sekvencemi poměrně velké množství funkcí, jejichž skládáním je možné vytvořit i dosti komplikované algoritmy. Navíc, což je pro implementaci mnoha algoritmů taktéž velmi důležité, podporuje knihovna Mori i vytváření a manipulaci s takzvanými línými sekvencemi (lazy sequence), v nichž se nové datové prvky vytváří či zjišťují až ve chvíli, kdy je to skutečně zapotřebí. A vzhledem k tomu, že mnohdy není zapotřebí přečíst všechny prvky nějaké líné sekvence, znamená to, že lze bez větších problémů pracovat i s nekonečnými sekvencemi (samozřejmě se však nesmíme snažit vyhodnotit/přečíst všechny prvky nekonečné sekvence, na druhou stranu je možné aplikovat funkci, která z jedné nekonečné líné sekvence vytvoří odlišnou línou sekvenci, a to bez toho, aby došlo k přeplnění operační paměti). Použití běžných sekvencí i líných sekvencí si ukážeme na několika demonstračních příkladech.
9. Základní funkce pro práci se sekvencemi
Naprostý základ pro práci se sekvencemi tvoří dvojice funkcí nazvaných first a rest. Funkce first vrací první prvek v sekvenci, popř. speciální hodnotu null v případě, že je sekvence prázdná. Funkce rest vrací zbylé prvky v sekvenci, popř. prázdnou sekvenci ve chvíli, kdy je sekvence prázdná či obsahuje pouze jeden prvek. U běžných sekvencí, například seznamů, jsou tyto funkce implementovány přímočaře, ovšem v případě lazy sekvencí se prvky vrácené pomocí funkce first vyhodnocují až za běhu aplikace, například pomocí nějaké generátorové funkce. Tímto způsobem je možné pracovat i s nekonečnými sekvencemi, u nichž už z principu nelze dopředu znát celkový počet prvků atd. (viz též závěr předchozí kapitoly).
10. Třetí demonstrační příklad: použití funkcí first a rest
Třetí demonstrační příklad je velmi jednoduchý. Nejprve jsou vytvořeny čtyři seznamy, přičemž třetí seznam obsahuje pouze jeden prvek a seznam čtvrtý je prázdný. Následně je vytvořena čtveřice vektorů se stejným počtem prvků. Délky (počty prvků) všech čtyř seznamů i všech čtyř vektorů jsou vypsány do konzole a posléze je ukázáno chování funkcí first a rest, popř. i kombinace těchto funkcí:
// ------------------------------------------------------------ // Knihovna Mori: demonstrační příklad číslo 3 // ------------------------------------------------------------ // vytvoření čtyř seznamu různé délky var list1 = mori.list(1,2,3); var list2 = mori.list(1,2); var list3 = mori.list(1); var list4 = mori.list(); // vytvoření čtyř vektoru různé délky var vector1 = mori.vector(1,2,3); var vector2 = mori.vector(1,2); var vector3 = mori.vector(1); var vector4 = mori.vector(); // ------------------------------------------------------------ // Výpis informaci o délkách seznamu a vektoru // ------------------------------------------------------------ console.log("List1 size: " + mori.count(list1)); console.log("List2 size: " + mori.count(list2)); console.log("List3 size: " + mori.count(list3)); console.log("List4 size: " + mori.count(list4)); console.log("Vector1 size: " + mori.count(vector1)); console.log("Vector2 size: " + mori.count(vector2)); console.log("Vector3 size: " + mori.count(vector3)); console.log("Vector4 size: " + mori.count(vector4)); // ------------------------------------------------------------ // Vyzkoušení funkce first // ------------------------------------------------------------ console.log("Using first:"); console.log("first list1: " + mori.first(list1)); console.log("first list2: " + mori.first(list2)); console.log("first list3: " + mori.first(list3)); console.log("first list4: " + mori.first(list4)); console.log("first vector1: " + mori.first(vector1)); console.log("first vector2: " + mori.first(vector2)); console.log("first vector3: " + mori.first(vector3)); console.log("first vector4: " + mori.first(vector4)); // ------------------------------------------------------------ // Vyzkoušení funkce rest // ------------------------------------------------------------ console.log("Using rest:"); console.log("rest list1: " + mori.rest(list1)); console.log("rest list2: " + mori.rest(list2)); console.log("rest list3: " + mori.rest(list3)); console.log("rest list4: " + mori.rest(list4)); console.log("rest vector1: " + mori.rest(vector1)); console.log("rest vector2: " + mori.rest(vector2)); console.log("rest vector3: " + mori.rest(vector3)); console.log("rest vector4: " + mori.rest(vector4)); // ------------------------------------------------------------ // Kombinace rest+first // ------------------------------------------------------------ console.log("Using rest+first:"); console.log("rest+first list1: " + mori.first(mori.rest(list1))); console.log("rest+first list2: " + mori.first(mori.rest(list2))); console.log("rest+first list3: " + mori.first(mori.rest(list3))); console.log("rest+first list4: " + mori.first(mori.rest(list4))); console.log("rest+first vector1: " + mori.first(mori.rest(vector1))); console.log("rest+first vector2: " + mori.first(mori.rest(vector2))); console.log("rest+first vector3: " + mori.first(mori.rest(vector3))); console.log("rest+first vector4: " + mori.first(mori.rest(vector4))); // ------------------------------------------------------------ // Kombinace rest+rest // ------------------------------------------------------------ console.log("Using rest+rest:"); console.log("rest+rest list1: " + mori.rest(mori.rest(list1))); console.log("rest+rest list2: " + mori.rest(mori.rest(list2))); console.log("rest+rest list3: " + mori.rest(mori.rest(list3))); console.log("rest+rest list4: " + mori.rest(mori.rest(list4))); console.log("rest+rest vector1: " + mori.rest(mori.rest(vector1))); console.log("rest+rest vector2: " + mori.rest(mori.rest(vector2))); console.log("rest+rest vector3: " + mori.rest(mori.rest(vector3))); console.log("rest+rest vector4: " + mori.rest(mori.rest(vector4))); // ------------------------------------------------------------ // Finito // ------------------------------------------------------------
Zdrojový kód HTML stránky použité pro spuštění třetího demonstračního příkladu v prohlížeči:
<!doctype html> <html> <head> <title>Mori tests #03</title> <meta charset="utf-8"> <script src="mori.js"></script> <script src="mori_03.js"></script> </head> <body> <h1>Mori tests #03</h1> </body> </html>
Na výstupu vygenerovaném demonstračním příkladem je nejzajímavější chování funkce first v případě, že se jí předá prázdná sekvence: vrátí se null. Naproti tomu funkce rest vrátí v případě sekvence s jedním prvkem prázdnou sekvenci; stejná hodnota se vrátí i ve chvíli, kdy se funkci rest na vstup předá prázdná sekvence (což je vlastně logické a očekávatelné chování, protože se do programového kódu nemusí zbytečně vkládat další podmínky):
List1 size: 3 List2 size: 2 List3 size: 1 List4 size: 0 Vector1 size: 3 Vector2 size: 2 Vector3 size: 1 Vector4 size: 0 Using first: first list1: 1 first list2: 1 first list3: 1 first list4: null first vector1: 1 first vector2: 1 first vector3: 1 first vector4: null Using rest: rest list1: (2 3) rest list2: (2) rest list3: () rest list4: () rest vector1: (2 3) rest vector2: (2) rest vector3: () rest vector4: ()
Obrázek 6: První část výstupu demonstračního příkladu.
Obrázek 7: Druhá část výstupu demonstračního příkladu.
11. Funkce range, take a map
Vlastnosti lazy sekvencí a taktéž nekonečných lazy sekvencí plně vyniknou až ve chvíli, kdy začneme používat funkce, které s těmito sekvencemi umí manipulovat, a to bez nutnosti výpočtu jednotlivých prvků sekvence. Nejprve se seznámíme s funkcí mori.range sloužící k vytvoření lazy sekvence čísel. Této funkci lze předat tři parametry mori.range(start, end, step) určující první číselnou hodnotu v sekvenci, limitní hodnotu a krok (ten udává počet prvků sekvence). Ovšem parametry je možné vynechat; když se například vynechá první parametr, bude mít první prvek v sekvenci hodnotu 0, když se vynechá parametr poslední, bude krok roven jedné. To znamená, že volání:
mori.range(10)
vytvoří lazy sekvenci:
(0 1 2 3 4 5 6 7 8 9)
jejíž prvky jsou však vyhodnoceny až ve chvíli, kdy je to zapotřebí (nebo taky nikdy!). Zajímavější je volání:
mori.range()
které vytvoří nekonečnou lazy sekvenci.
Funkce mori.take na svém vstupu očekává celé číslo n a libovolnou (lazy) sekvenci. Vrátí se taktéž lazy sekvence, mající ovšem maximálně n prvků původní sekvence. Prvky se nevyhodnocují, proto tato funkce bez problémů pracuje i s nekonečnými sekvencemi.
Třetí funkcí je funkce mori.map, která dokáže pro každý prvek vstupní (lazy) sekvence zavolat jinou zvolenou funkci. Výsledkem mori.map je druhá lazy sekvence. Ovšem pozor: zvolená funkce se pro prvky nevolá ihned, ale „líně“ až ve chvíli, kdy je to skutečně zapotřebí (v mnoha případech tedy nikdy). Proto i funkce map může pracovat s nekonečnými sekvencemi.
12. Čtvrtý demonstrační příklad: použití funkcí range, take a map
Podívejme se nyní na okomentovaný čtvrtý demonstrační příklad, v němž se kombinují možnosti výše popsaných funkcí range, map a take. Za povšimnutí stojí zejména možnost využít ve funkci map anonymní funkci či libovolnou (pojmenovanou) funkci. Tyto funkce nejsou v příkladu zcela čisté, protože provádějí výpis na konzoli. Právě díky tomu je patrné, kdy se tyto funkce ve skutečnosti volají, tj. kdy se z lazy sekvence stane běžná (vyhodnocená, realizovaná) sekvence:
// ------------------------------------------------------------ // Knihovna Mori: demonstrační příklad číslo 4 // ------------------------------------------------------------ // konstrukce nekonečné lazy sekvence var sequence1 = mori.range(); // z nekonečné lazy sekvence získáme konečnou línou sekvenci s 10 prvky var sequence2 = mori.take(10, sequence1); // realizace druhé sekvence + její výpis console.log("sequence2=" + sequence2); // funkce použitá ve funkcích map function twice(element) { console.log('processing:', element); return 2 * element; } // mapujeme uživatelskou funkci na *konečnou* sekvenci var sequence3 = mori.map(twice, sequence2); // mapujeme uživatelskou anonymní funkci na *konečnou* sekvenci var sequence4 = mori.map(function(element) { console.log('processing:', element); return -2 * element }, sequence2); // obě nové vytvořené sekvence vypíšeme // (tím se 'realizuji') console.log("sequence3=" + sequence3); console.log("sequence4=" + sequence3); // nyní mapujeme uživatelskou funkci na *nekonečnou* sekvenci var sequence5 = mori.map(twice, sequence1); // nyní mapujeme uživatelskou anonymní funkci na *nekonečnou* sekvenci var sequence6 = mori.map(function(element) { console.log('processing:', element); return -1 * element }, sequence1); // obě nově vytvořené sekvence vypíšeme // (tím se 'realizuji') console.log("sequence5 begins with: " + mori.take(10, sequence5)); console.log("sequence6 begins with: " + mori.take(10, sequence6)); // mapování může mít několik "vrstev", stále se však jedna o lazy // sekvence, které se prozatím nerealizuji (mezikrok je taktéž nekonečná sekvence!) var sequence7 = mori.map(twice, mori.map(twice, sequence1)); // sekvence vypíšeme // (tím se 'realizuje') console.log("sequence7 begins with: " + mori.take(10, sequence7)); // ------------------------------------------------------------ // Finito // ------------------------------------------------------------
Zdrojový kód HTML stránky použité pro spuštění čtvrtého demonstračního příkladu v prohlížeči:
<!doctype html> <html> <head> <title>Mori tests #04</title> <meta charset="utf-8"> <script src="mori.js"></script> <script src="mori_03.js"></script> </head> <body> <h1>Mori tests #04</h1> </body> </html>
Obrázek 8: Výsledek běhu demonstračního příkladu..
13. Repositář se zdrojovými kódy všech dnešních demonstračních příkladů
Demonstrační příklady, na nichž jsme si ukazovali vlastnosti knihovny Mori, byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/presentations. V tabulce zobrazené pod tímto odstavcem naleznete na zdrojové kódy těchto příkladů přímé odkazy:
14. Odkazy na Internetu
- Mori na GitHubu
https://github.com/swannodette/mori - Mori: popis API (dokumentace)
http://swannodette.github.io/mori/ - Mori: Benchmarking
https://github.com/swannodette/mori/wiki/Benchmarking - Functional data structures in JavaScript with Mori
http://sitr.us/2013/11/04/functional-data-structures.html - Immutable.js
https://facebook.github.io/immutable-js/ - Persistent data structure
https://en.wikipedia.org/wiki/Persistent_data_structure - Understanding Clojure's Persistent Vectors, pt. 1
http://hypirion.com/musings/understanding-persistent-vector-pt-1 - Hash array mapped trie (Wikipedia)
https://en.wikipedia.org/wiki/Hash_array_mapped_trie - Java theory and practice: To mutate or not to mutate?
http://www.ibm.com/developerworks/java/library/j-jtp02183/index.html - Efficient persistent (immutable) data structures
https://persistent.codeplex.com/ - Netscape Enterprise Server (Wikipedia)
https://en.wikipedia.org/wiki/Netscape_Enterprise_Server - SSJS Reference Guide (Server-Side JavaScript)
http://docs.oracle.com/cd/E19957–01/816–6410–10/816–6410–10.pdf - Atom: moderní textový editor
http://www.root.cz/clanky/atom-moderni-textovy-editor/ - Atom: moderní textový editor (dokončení)
http://www.root.cz/clanky/atom-moderni-textovy-editor-dokonceni/ - Propojení světa LISPu se světem JavaScriptu s využitím transpřekladače Wisp
http://www.root.cz/clanky/propojeni-sveta-lispu-se-svetem-javascriptu-s-vyuzitim-transprekladace-wisp/ - Propojení světa LISPu se světem JavaScriptu s využitím transpřekladače Wisp (2.část)
http://www.root.cz/clanky/propojeni-sveta-lispu-se-svetem-javascriptu-s-vyuzitim-transprekladace-wisp-2-cast/ - 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/