Obsah
1. Podpora funkcionálního programovaní v jazyku Lua s využitím knihovny Moses (pokračování)
3. Funkce mean, median, min a max
4. Pokročilejší zpracování polí: flatten a compact
5. Vytvoření párů hodnot funkcí xprod
6. Funkce reduce aplikovaná na tabulku
7. Zpracování prvků tabulky v opačném směru funkcí reduceRight
8. Rozdělení tabulky funkcí vyššího řádu groupBy
9. Iterace prvky tabulky pomocí funkcí each
10. Rozdíl mezi funkcemi each a eachi
11. Další funkce vyššího řádu procházející tabulkou: map
12. Zřetězení operací nad tabulkou s využitím chain
13. Složitější příklady zřetězení operací
14. Funkce vyššího řádu mapReduce
15. Rozdíl mezi funkcemi mapReduce a mapReduceRight
16. Iterace prvky tabulky funkcemi sortedk a sortedv
17. Obsah závěrečného článku o knihovně Moses
18. Repositář s demonstračními příklady
19. Články o programovacím jazyce Lua i o technologii LuaJITu
1. Podpora funkcionálního programovaní v jazyku Lua s využitím knihovny Moses (pokračování)
Ve druhém článku o knihovně Moses se budeme zpočátku zabývat těmi funkcemi, které jsou primárně určené pro zpracování tabulek. Připomeňme si na tomto místě, že použitá terminologie může být matoucí, protože se pod pojmem tabulka ve skutečnosti skrývá datová struktura známá v jiných jazycích pod jménem slovník. V následující tabulce (sic) jsou terminologické rozdíly mezi tandemem Lua+Moses a většinou dalších programovacích jazyků shrnuty:
# | Termín použitý v Moses | Běžný význam |
---|---|---|
1 | array | list, set |
2 | table | dictionary |
V tomto článku budou popsány následující funkce (a metody) poskytované knihovnou Moses:
# | Funkce | Kapitola |
---|---|---|
1 | sum | 2 |
2 | product | 2 |
3 | mean | 3 |
4 | median | 3 |
5 | min | 3 |
6 | max | 3 |
7 | flatten | 4 |
8 | compact | 4 |
9 | xprod | 5 |
10 | reduce | 6 |
11 | reduceRight | 7 |
12 | groupBy | 8 |
13 | each | 9 |
14 | eachi | 10 |
15 | map | 11 |
16 | chain | 12 |
17 | mapReduce | 14 |
18 | mapReduceRight | 15 |
19 | sortedk | 16 |
20 | sortedv | 16 |
2. Funkce sum a product
Dvě sice jednoduché, ovšem prakticky velmi dobře použitelné funkce, které v knihovně Moses nalezneme, se jmenují sum a product. První z těchto funkcí je určena pro součet všech prvků pole, druhá pro postupný součin všech prvků. Použití obou zmíněných funkcí je snadné:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- tisk obsahu pole function printArrayInLine(array) -- test na prázdné pole if #array == 0 then print("*empty*") return end for i, value in ipairs(array) do io.write(value) if i ~= #array then io.write(", ") end end print() end -- oddělení obsahu function printSeparator() print("--------------------------------------------") end -- první vstupní pole obsahuje sekvenci čísel 1 až 10 a = range(10) printSeparator() print("Original array") printArrayInLine(a) print() -- tisk součtu (sumace) a součinu všech hodnot v poli print("Sum=" .. sum(a)) print("Product=" .. product(a))
Tento demonstrační příklad by měl po svém spuštění vypsat součet prvků z aritmetické posloupnosti 1 až 10. Ihned poté se vypočte a zobrazí i součin všech těchto deseti prvků:
-------------------------------------------- Original array 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 Sum=55 Product=3628800
Pokud se ovšem v sekvenci bude nabízet nulový prvek, vrátí funkce product (pochopitelně) i nulový výsledek:
-- druhé vstupní pole obsahuje sekvenci čísel 0 až 10 b = range(0, 10) printSeparator() print("Original array") printArrayInLine(b) print() -- tisk součtu (sumace) a součinu všech hodnot v poli print("Sum=" .. sum(b)) print("Product=" .. product(b))
Poslední řádek skutečně v tomto případě zobrazuje nulu:
-------------------------------------------- Original array 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 Sum=55 Product=0
Vyzkoušejme si ještě, co se stane v případě, kdy je vstupní pole prázdné, tj. když neobsahuje žádné prvky:
-- vstupní pole je prázné a = {} printSeparator() print("Original array") printArrayInLine(a) print() -- tisk součtu (sumace) a součinu všech hodnot v poli print("Sum=" .. sum(a)) print("Product=" .. product(a))
Zde nastane zvláštní situace – suma bude rovna nule, ovšem součin roven jedné:
-------------------------------------------- Original array *empty* Sum=0 Product=1
Operaci prováděnou funkcí product můžeme využít pro výpočet faktoriálu, resp. přesněji řečeno tabulky faktoriálů pro vstupní hodnoty 0 až 10:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- výpočet faktoriálu vstupních hodnot 0 až 10 for n = 0, 10 do -- konstrukce pole a = range(1, n) -- výpočet a tisk produktu hodnot 1..n == n! print(n, product(a)) end
Výsledná tabulka vypočtená předchozím demonstračním příkladem:
0 0 1 1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880 10 3628800
3. Funkce mean, median, min a max
Knihovna Moses obsahuje i funkce určené pro výpočet průměru a mediánu; další dvě funkce slouží pro nalezení prvku s minimální, resp. s maximální hodnotou. Matematický význam těchto funkcí by měl být zřejmý, takže si je můžeme přímo otestovat:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- tisk obsahu pole function printArrayInLine(array) -- test na prázdné pole if #array == 0 then print("*empty*") return end for i, value in ipairs(array) do io.write(value) if i ~= #array then io.write(", ") end end print() end -- oddělení obsahu function printSeparator() print("--------------------------------------------") end -- vytvoření a naplnění pole o sto prvcích a={} for i = 1, 100 do a[i] = math.random(10) end -- vytištění původního pole printSeparator() print("Original array") printArrayInLine(a) -- výpočet a tisk základních statistických veličin print() print("Mean: " .. mean(a)) print("Median: " .. median(a)) print("Min: " .. min(a)) print("Max: " .. max(a))
Pole o sto prvcích je naplněno náhodnými hodnotami v rozsahu 1 až 10, čemuž odpovídají i vypočtené hodnoty průměru a mediánu:
-------------------------------------------- Original array 9, 4, 8, 8, 10, 2, 4, 8, 3, 6, 5, 7, 4, 6, 10, 10, 7, 8, 2, 7, 1, 3, 2, 9, 2, 5, 2, 2, 10, 3, 6, 9, 7, 3, 7, 6, 5, 10, 3, 8, 6, 8, 5, 9, 3, 4, 9, 10, 1, 10, 6, 1, 2, 7, 9, 4, 1, 1, 5, 1, 3, 10, 10, 9, 3, 6, 4, 8, 6, 7, 6, 1, 5, 10, 10, 8, 3, 8, 7, 4, 7, 2, 5, 9, 9, 4, 3, 9, 4, 7, 10, 6, 7, 9, 5, 10, 4, 9, 7, 10 Mean: 5.97 Median: 6.0 Min: 1 Max: 10
Pokud příklad upravíme tak, aby se generovaly pseudonáhodné hodnoty od 0 do 10 (tedy včetně nuly), bude průměr a medián odlišný:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- tisk obsahu pole function printArrayInLine(array) -- test na prázdné pole if #array == 0 then print("*empty*") return end for i, value in ipairs(array) do io.write(value) if i ~= #array then io.write(", ") end end print() end -- oddělení obsahu function printSeparator() print("--------------------------------------------") end -- vytvoření a naplnění pole o sto prvcích a={} for i = 1, 100 do -- náhodná hodnota v rozsahu 0 až 10 a[i] = math.random(0, 10) end -- vytištění původního pole printSeparator() print("Original array") printArrayInLine(a) -- výpočet a tisk základních statistických veličin print() print("Mean: " .. mean(a)) print("Median: " .. median(a)) print("Min: " .. min(a)) print("Max: " .. max(a))
Nyní bude průměr roven 5.5 a medián (hodnota rozdělující setříděnou sekvenci v polovině) bude roven pěti:
-------------------------------------------- Original array 9, 4, 8, 8, 10, 2, 3, 8, 3, 6, 5, 6, 4, 5, 10, 10, 6, 7, 1, 6, 0, 2, 1, 8, 1, 4, 1, 1, 10, 2, 5, 9, 6, 3, 7, 5, 5, 10, 3, 8, 5, 8, 4, 9, 3, 3, 8, 10, 0, 10, 5, 0, 2, 7, 9, 3, 0, 0, 5, 0, 2, 10, 9, 9, 2, 5, 4, 8, 5, 7, 5, 0, 4, 10, 10, 7, 3, 8, 7, 3, 7, 1, 4, 9, 9, 3, 2, 9, 3, 7, 10, 6, 7, 9, 4, 10, 4, 8, 7, 10 Mean: 5.5 Median: 5.0 Min: 0 Max: 10
Ještě si ukažme, jak budou výpočty vypadat pro prázdné vstupní pole:
-- prázdné vstupní pole a={} -- vytištění původního pole printSeparator() print("Original array") printArrayInLine(a) -- výpočet a tisk základních statistických veličin print() print("Mean: " .. mean(a)) print("Median: " .. (median(a) or "nil")) print("Min: " .. (min(a) or "nil")) print("Max: " .. (max(a) or "nil"))
Zde dochází k tomu, že průměr je roven NaN (protože se interně pravděpodobně dělí nula nulou), kdežto ostatní statistické veličiny budou rovny nil (prázdná hodnota):
-------------------------------------------- Original array *empty* Mean: -nan Median: nil Min: nil Max: nil
4. Pokročilejší zpracování polí: flatten a compact
Další dvě funkce mohou být velmi užitečné při zpracování polí (či slovníků), jejichž data byla importována například z formátu XML, JSON či EDT. Typicky se jedná o strukturovaná data představovaná poli/slovníky, jejichž prvky jsou další pole/slovníky. První z funkcí se jmenuje flatten a umožňuje původní hierarchickou datovou strukturu „zploštit“ buď do běžné jednorozměrné (lineární tabulky) nebo lze v případě potřeby „zploštění“ provést pouze na nejvyšší úrovni (a ostatní hierarchické struktury zachovat). Ukažme si nejdříve první případ, tj. úplné zploštění obsahu tabulky pomocí funkce nazvané příznačně flatten. Povšimněte si, že v příkladu používáme novou pomocnou funkci pojmenovanou nestedArray, která akceptuje tabulku s libovolnou interní strukturou a zobrazí její obsah ve formě S-výrazu používaného v mnoha LISPovských programovacích jazycích:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- získaní textové podoby obsahu pole obsahujícího další vnořená pole function nestedArray(array) if type(array) == 'table' then local s = '(' -- použít LISPovské s-výrazy for i, v in ipairs(array) do -- oddělovač if i > 1 then s = s .. ' ' end -- pole mohou být vnořena s = s .. nestedArray(v) end return s .. ')' else return tostring(array) end end -- oddělení obsahu function printSeparator() print("--------------------------------------------") end
Vlastní zploštění se provádí zde:
-- původní pole a = {1, 2, 3, {4, 5, 6, {7, 8, 9}, {10, 11}}, 12} printSeparator() -- tisk původního pole print("Original array") print(nestedArray(a)) print() -- tisk zploštělého pole printSeparator() print("Flatten array") print(nestedArray(flatten(a)))
Příklad po svém spuštění nejdříve vypíše obsah původní tabulky (zde je patrná hierarchická a rekurzivní struktura) a následně obsah stejné tabulky, ovšem po zploštění:
-------------------------------------------- Original array (1 2 3 (4 5 6 (7 8 9) (10 11)) 12) -------------------------------------------- Flatten array (1 2 3 4 5 6 7 8 9 10 11 12)
Můžeme mít samozřejmě tabulku s mnohem hlubší strukturou, například:
-- původní pole b = {"a", {"b", {"c", {"d", {"e", {"f", {"g"}}}}}}} printSeparator() -- tisk původního pole print("Original array") print(nestedArray(b)) print() -- tisk zploštělého pole print("Flatten array") print(nestedArray(flatten(b)))
Výsledek po spuštění této části demonstračního příkladu:
-------------------------------------------- Original array (a (b (c (d (e (f (g))))))) Flatten array (a b c d e f g)
Pokud ovšem funkci flatten předáme další (nepovinný) parametr, provede se zploštění pouze na nejvyšší úrovni hierarchie – funkce nebude aplikována rekurzivně i na všechny prvky tabulky (a na případné vnořené tabulky). Opět si ukažme, jak taková operace vypadaná v praxi:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- získaní textové podoby obsahu pole obsahujícího další vnořená pole function nestedArray(array) if type(array) == 'table' then -- použít LISPovské s-výrazy local s = '(' for i, v in ipairs(array) do -- oddělovač if i > 1 then s = s .. ' ' end -- pole mohou být vnořena s = s .. nestedArray(v) end return s .. ')' else return tostring(array) end end -- oddělení obsahu function printSeparator() print("--------------------------------------------") end -- původní pole a = {1, 2, 3, {4, 5, 6, {7, 8, 9}, {10, 11}}, 12} printSeparator() -- tisk původního pole print("Original array") print(nestedArray(a)) print() printSeparator() -- tisk zploštělého pole print("Flatten array") print(nestedArray(flatten(a, true)))
Opět se nejdříve vypíše původní struktura tabulky a posléze struktura po zploštění. Povšimněte si, že vnořené tabulky nejsou v tomto případě zploštěny:
-------------------------------------------- Original array (1 2 3 (4 5 6 (7 8 9) (10 11)) 12) -------------------------------------------- Flatten array (1 2 3 4 5 6 (7 8 9) (10 11) 12)
Tatáž operace je ještě lépe viditelná na tabulce, která interně připomíná lineárně vázaný seznam známý z programovacího jazyka LISP:
-- původní pole b = {"a", {"b", {"c", {"d", {"e", {"f", {"g"}}}}}}} printSeparator() -- tisk původního pole print("Original array") print(nestedArray(b)) print() -- tisk zploštělého pole print("Flatten array") print(nestedArray(flatten(b, true)))
Výsledek zploštění pouze na nejvyšší úrovni:
-------------------------------------------- Original array (a (b (c (d (e (f (g))))))) Flatten array (a b (c (d (e (f (g))))))
Nakonec si ukažme postupné zplošťování obsahu tabulky až do chvíle, kdy je nějaká varianta tabulky totožná s variantou zploštěnou. Začátek zdrojového kódu demonstračního příkladu je stále stejný:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- získaní textové podoby obsahu pole obsahujícího další vnořená pole function nestedArray(array) if type(array) == 'table' then -- použít LISPovské s-výrazy local s = '(' for i, v in ipairs(array) do -- oddělovač if i > 1 then s = s .. ' ' end -- pole mohou být vnořena s = s .. nestedArray(v) end return s .. ')' else return tostring(array) end end -- oddělení obsahu function printSeparator() print("--------------------------------------------") end
Zplošťování probíhá v programové smyčce typu repeat-until až do chvíle, kdy je zploštěná tabulka totožná s mezitabulkou (a kdy tudíž již nemá smysl funkci flatten neustále spouštět):
-- původní pole a = {1, 2, 3, {4, 5, 6, {7, 8, 9, {10, 11}}, {12, 13, {14, 15}}}, 16} printSeparator() -- tisk původního pole print("Original array") print(nestedArray(a)) printSeparator() -- tisk postupně "zplošťovaného" pole print("Flatten array") repeat b = a a = flatten(a, true) print(nestedArray(b)) until isEqual(a, b)
Výsledek po spuštění příkladu:
-------------------------------------------- Original array (1 2 3 (4 5 6 (7 8 9 (10 11)) (12 13 (14 15))) 16) -------------------------------------------- Flatten array (1 2 3 (4 5 6 (7 8 9 (10 11)) (12 13 (14 15))) 16) (1 2 3 4 5 6 (7 8 9 (10 11)) (12 13 (14 15)) 16) (1 2 3 4 5 6 7 8 9 (10 11) 12 13 (14 15) 16) (1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16)
Tutéž operaci provedeme s druhou tabulkou, kterou již známe (jakýsi LISPovský seznam či jeho obdoba):
-- původní pole a = {"a", {"b", {"c", {"d", {"e", {"f", {"g"}}}}}}} printSeparator() -- tisk původního pole print("Original array") print(nestedArray(a)) print() -- tisk postupně "zplošťovaného" pole print("Flatten array") repeat b = a a = flatten(a, true) print(nestedArray(b)) until isEqual(a, b)
Výsledek postupného splošťování:
-------------------------------------------- Original array (a (b (c (d (e (f (g))))))) Flatten array (a (b (c (d (e (f (g))))))) (a b (c (d (e (f (g)))))) (a b c (d (e (f (g))))) (a b c d (e (f (g)))) (a b c d e (f (g))) (a b c d e f (g)) (a b c d e f g)
Druhou funkcí, kterou si v této kapitole popíšeme, je funkce nazvaná compact. Tato funkce z tabulky odstraní „nepravdivé prvky“, konkrétně prvky mající hodnotu false nebo nil:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- tisk obsahu pole function printArrayInLine(array) -- test na prázdné pole if #array == 0 then print("*empty*") return end for i, value in ipairs(array) do io.write(tostring(value)) if i ~= #array then io.write(", ") end end print() end -- oddělení obsahu function printSeparator() print("--------------------------------------------") end -- tisk původního pole i pole "kompaktního" function printCompacedArray(a) printSeparator() print("original array") printArrayInLine(a) print("compacted array") printArrayInLine(compact(a)) print() end printCompacedArray({}) printCompacedArray({1, 2, 3, 4}) printCompacedArray(zeros(10)) printCompacedArray({true, false, true, false, true, false}) printCompacedArray({"foo", nil, "bar", nil, nil, "baz", nil, nil, nil})
Povšimněte si, že pokud tabulka prvky nil nebo false neobsahuje, není pozměněna:
-------------------------------------------- original array *empty* compacted array *empty* -------------------------------------------- original array 1, 2, 3, 4 compacted array 1, 2, 3, 4 -------------------------------------------- original array 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 compacted array 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -------------------------------------------- original array true, false, true, false, true, false compacted array true, true, true -------------------------------------------- original array foo compacted array foo, bar, baz
5. Vytvoření párů hodnot funkcí xprod
V některých algoritmech může být užitečné zkombinovat dvojici tabulek, a to takovým způsobem, že výsledkem bude další tabulka, jejíž prvky budou postupně obsahovat všechny možné kombinace prvků z původních dvou tabulek. Nejlépe si chování osvětlíme na dvou vstupních tabulkách, které mohou mít rozdílný počet prvků (3, resp. 5 prvků). Výsledkem bude tabulka s celkem patnácti prvky, kde každý prvek je dvojicí:
-------------------------------------------- first original array foo, bar, baz second original array 1, 2, 3, 4, 5 generated pairs ((foo 1) (foo 2) (foo 3) (foo 4) (foo 5) (bar 1) (bar 2) (bar 3) (bar 4) (bar 5) (baz 1) (baz 2) (baz 3) (baz 4) (baz 5))
Tento výsledek byl získán spuštěním následujícího demonstračního příkladu:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- tisk obsahu pole function printArrayInLine(array) -- test na prázdné pole if #array == 0 then print("*empty*") return end for i, value in ipairs(array) do io.write(value) if i ~= #array then io.write(", ") end end print() end -- získaní textové podoby obsahu pole obsahujícího další vnořená pole function nestedArray(array) if type(array) == 'table' then -- použít LISPovské s-výrazy local s = '(' for i, v in ipairs(array) do -- oddělovač if i > 1 then s = s .. ' ' end -- pole mohou být vnořena s = s .. nestedArray(v) end return s .. ')' else return tostring(array) end end -- oddělení obsahu function printSeparator() print("--------------------------------------------") end a = {"foo", "bar", "baz"} b = range(5) p = xprod(a, b) printSeparator() print("first original array") printArrayInLine(a) print() print("second original array") printArrayInLine(b) print() print("generated pairs") print(nestedArray(p))
V případě, že je jedno ze vstupních polí prázdné, bude prázdné i pole výstupní, protože prvky druhého pole není možné zkombinovat s prvky prázdného pole. O tom se ostatně můžeme velmi snadno přesvědčit:
a = range(100) b = {} p = xprod(a, b)
-------------------------------------------- first original array 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100 second original array *empty* generated pairs ()
Pokud nějaká tabulka obsahuje prvek s hodnotou nil, je vytváření párů na tomto prvku zastaveno:
a = range(10) b = {"a", "b", nil, "c"} p = xprod(a, b) printSeparator() print("first original array") printArrayInLine(a) print() print("second original array") printArrayInLine(b) print() print("generated pairs") print(nestedArray(p))
S výsledky:
first original array 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 second original array a, b, generated pairs ((1 a) (1 b) (2 a) (2 b) (3 a) (3 b) (4 a) (4 b) (5 a) (5 b) (6 a) (6 b) (7 a) (7 b) (8 a) (8 b) (9 a) (9 b) (10 a) (10 b))
6. Funkce reduce aplikovaná na tabulku
Ve většině programovacích jazyků inspirovaných funkcionálním programováním se, jak již ostatně víme z článku o knihovně Lua Fun, velmi často setkáme i s funkcí nazvanou reduce nebo fold, popř. s různými alternativami s podobnými operacemi. Základní operací tohoto typu je funkce vyššího řádu nazvaná reduce, která postupně zpracovává všechny prvky pole nebo slovníku zleva doprava a aplikuje na každý prvek a akumulovanou hodnotu nějakou funkci (a právě to tedy mj. znamená, že reduce je funkcí vyššího řádu). Výsledkem je v každé iteraci nová hodnota akumulátoru a po projití celé vstupní sekvence je výsledná hodnota uložená v akumulátoru současně i návratovou hodnotou funkce. Tuto funkci můžeme využít například při výpočtu faktoriálu, protože při výpočtu faktoriálu nějakého n postačuje pomocí range vytvořit pole o n prvcích a posléze jeho prvky postupně pronásobit. Implementace založená na pojmenované (neanonymní) funkci může vypadat následovně:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- pomocná funkce pro součin jejích dvou parametrů function multiply(x, y) return x * y end -- výpočet faktoriálu vstupních hodnot 0 až 10 for n = 0, 10 do -- konstrukce pole a = range(1, n) -- výpočet produktu hodnot 1..n == n! prod = reduce(a, multiply) -- tisk produktu hodnot 1..n == n! print(n, prod) end
Výsledná tabulka vytištěná demonstračním příkladem:
0 0 1 1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880 10 3628800
Totéž ovšem můžeme provést i bez nutnosti explicitně pojmenovat funkci multiply; použijeme namísto toho funkci anonymní:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- výpočet faktoriálu vstupních hodnot 0 až 10 for n = 0, 10 do -- konstrukce pole a = range(1, n) -- výpočet produktu hodnot 1..n == n! -- (použila se anonymní funkce) prod = reduce(a, function (x, y) return x * y end) -- tisk produktu hodnot 1..n == n! print(n, prod) end
Výsledky budou totožné:
0 0 1 1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880 10 3628800
Třetí příklad ukazuje způsob spojení všech prvků v poli do jediného řetězce, tedy o variantu na funkci typu join:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- vstupní pole a = {"foo", "bar", "baz"} -- postupné zpracování prvků pole zleva s akumulací výsledku print(reduce(a, function (x, y) return x .. " " .. y end))
Po spuštění tohoto příkladu by se měl zobrazit jediný řetězec tvořený třemi slovy:
foo bar baz
7. Zpracování prvků tabulky v opačném směru funkcí reduceRight
Funkce reduce popsaná v předchozí kapitole zpracovávala prvky pole nebo slovníku (s akumulací mezivýsledků) od začátku pole/slovníku směrem k jeho konci. Proto je taktéž tato funkce známá pod jménem foldl, kde „l“ značí „from left“. V některých případech je však nutné sekvenci prvků zpracovávat od prvku posledního směrem k prvku prvnímu (i když u klasických sekvencí se jedná o nepřirozenou a mnohdy ani nepodporovanou operaci – speciálně to platí pro nekonečné sekvence vytvářené nějakým generátorem). Funkce provádějící tuto operaci se nazývá foldr nebo taktéž reduceRight. V dalším demonstračním příkladu je ukázáno, jak se tyto dvě funkce (reduce a reduceRight) od sebe odlišují při zpracování pole obsahujícího celočíselné prvky s hodnotami 1 až 10. Prvky postupně čteme zprava či zleva, převádíme na řetězec a spojujeme do většího řetězce:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- vstupní pole a = range(10) -- postupné zpracování prvků pole zleva s akumulací výsledku print("reduceleft") print(reduce(a, function (x, y) return tostring(x) .. " " .. tostring(y) end)) print() -- postupné zpracování prvků pole zprava s akumulací výsledku print("reduceright") print(reduceright(a, function (x, y) return tostring(x) .. " " .. tostring(y) end))
Výsledky získané po spuštění tohoto demonstračního příkladu. Můžeme na nich vidět, že se prvky skutečně zpracovávaly odlišným způsobem:
reduceLeft 1 2 3 4 5 6 7 8 9 10 reduceRight 10 9 8 7 6 5 4 3 2 1
-- postupné zpracování prvků pole zleva s akumulací výsledku print("reduceleft") print(reduce(a, function (x, y) return x .. " " .. y end)) print() -- postupné zpracování prvků pole zprava s akumulací výsledku print("reduceright") print(reduceright(a, function (x, y) return x .. " " .. y end))
8. Rozdělení tabulky funkcí vyššího řádu groupBy
V knihovně Lua Fun byla použita funkce partition, která nahrazovala dvojí použití funkce filter; poprvé pro splněnou podmínku, podruhé pro podmínku nesplněnou. Existuje však obecnější přístup rozdělení prvků tabulky nebo pole a tento přístup je představován funkcí nazvanou groupBy. Tato funkce vyššího řádu volá pro každý prvek pole jinou funkci a použije návratovou hodnotu této funkce pro označení daného prvku. Následně jsou prvky se stejnou značkou (tag, label) seskupeny a výsledkem je slovník, jehož klíče jsou tvořeny značkami a hodnoty poli prvků. Podívejme se nyní na demonstrační příklad:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- tisk obsahu pole function printArrayInLine(array) -- test na prázdné pole if #array == 0 then print("*empty*") return end for i, value in ipairs(array) do io.write(value) if i ~= #array then io.write(", ") end end print() end -- tisk tabulky, jejíž prvky tvoří pole function printTable(table) for key, values in pairs(table) do -- tisk klíče io.write(key .. ":\t") -- tisk hodnot printArrayInLine(values) end end -- oddělení obsahu function printSeparator() print("--------------------------------------------") end -- pomocná funkce, která na základě hodnoty prvku vrátí návěští function labelItem(x) if x % 2 == 0 then return "even" else return "odd" end end -- původní sekvence a = range(10) -- tisk původní sekvence printSeparator() print("original array") printArrayInLine(a) print() -- rozdělení prvků v sekvenci podle návěští sudá/lichá g = groupBy(a, labelItem) -- tisk prvků rozdělených do skupin printSeparator() print("groupedBy odd/even") printTable(g)
V tomto demonstračním příkladu jsou prvky postupně označeny štítky „odd“ a „even“, takže výsledkem je skutečně slovník, přičemž každý prvek slovníku obsahuje pětici hodnot:
-------------------------------------------- original array 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 -------------------------------------------- groupedBy odd/even even: 2, 4, 6, 8, 10 odd: 1, 3, 5, 7, 9
Další příklad rozdělí všechny prvky ze vstupní tabulky podle jejich typu. Datový typ je v programovacím jazyku Lua možné zjistit přímo za běhu aplikace:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- tisk obsahu pole function printArrayInLine(array) -- test na prázdné pole if #array == 0 then print("*empty*") return end for i, value in ipairs(array) do io.write(tostring(value)) if i ~= #array then io.write(", ") end end print() end -- tisk tabulky, jejíž prvky tvoří pole function printTable(table) for key, values in pairs(table) do -- tisk klíče io.write(key .. ":\t") -- tisk hodnot printArrayInLine(values) end end -- oddělení obsahu function printSeparator() print("--------------------------------------------") end -- původní sekvence a = {1, 2, 3, 4, "a", "b", "c", nil, true, false, 42, function () end, print} -- tisk původní sekvence printSeparator() print("original array") printArrayInLine(a) print() -- rozdělení prvků v sekvenci podle jejich typu g = groupBy(a, type) -- tisk prvků rozdělených do skupin podle jejich typu printSeparator() print("groupedBy type") printTable(g)
Výsledek vypadá následovně:
-------------------------------------------- original array 1, 2, 3, 4, a, b, c, -------------------------------------------- groupedBy type boolean: true, false string: a, b, c number: 1, 2, 3, 4, 42 function: function: 0x558e68e62e30, function: 0x7f9bfb7316b0
9. Iterace prvky tabulky pomocí funkcí each
V případě, že je nutné postupně provést nějakou operaci nad všemi prvky pole nebo slovníku, můžeme k tomuto účelu použít funkci vyššího řádu, která se jmenuje each. Této funkci se předá pole nebo slovník a taktéž jiná funkce (buď jméno funkce nebo tělo funkce anonymní), která se má vykonat nad každým prvkem vstupu. To mj. znamená, že z programového kódu dokážeme odstranit některé typy programových smyček typu for-each. Příkladem může být mj. i úprava kódu funkce printTable. Povšimněte si, že uvnitř each je použita anonymní funkce:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- tisk tabulky, jejíž prvky tvoří pole function printTable(table) for key, values in pairs(table) do -- tisk klíče io.write(key .. ":\t") -- tisk hodnot each(values, function (value) io.write(tostring(value) .. " ") end) print() end end -- oddělení obsahu function printSeparator() print("--------------------------------------------") end
Tentýž trik je použit i při výpisu původního pole:
-- původní sekvence a = {1, 2, 3, 4, "a", "b", "c", nil, true, false, 42, function () end, print} -- tisk původní sekvence funkcí each printSeparator() print("original array") each(a, function(value, index) print(value) end) print() -- rozdělení prvků v sekvenci podle jejich typu g = groupBy(a, type) -- tisk prvků rozdělených do skupin podle jejich typu printSeparator() print("groupedBy type") printTable(g)
Výsledky vypsané po spuštění demonstračního příkladu jsou podobné příkladu z předchozí kapitoly:
-------------------------------------------- original array 1 2 3 4 a b c true false 42 function: 0x55603df42e00 function: 0x7fa6a6f706b0 -------------------------------------------- groupedBy type number: 1 2 3 4 42 string: a b c function: function: 0x55603df42e00 function: 0x7fa6a6f706b0 boolean: true false
Ve skutečnosti můžeme jít ještě dále a ve funkci printTable odstranit obě programové smyčky:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- tisk tabulky, jejíž prvky tvoří pole function printTable(table) -- tisk prvků tabulky pomocí funkce each each(table, function(values, key) -- tisk klíče io.write(key .. ":\t") -- tisk hodnot each(values, function (value) io.write(tostring(value) .. " ") end) print() end ) end -- oddělení obsahu function printSeparator() print("--------------------------------------------") end -- původní sekvence a = {1, 2, 3, 4, "a", "b", "c", nil, true, false, 42, function () end, print} -- tisk původní sekvence funkcí each printSeparator() print("original array") each(a, function(value, index) print(value) end) print() -- rozdělení prvků v sekvenci podle jejich typu g = groupBy(a, type) printSeparator() print("groupedBy type") printTable(g)
Zobrazené výsledky:
-------------------------------------------- original array 1 2 3 4 a b c true false 42 function: 0x5590b5e69e00 function: 0x7fe95625b6b0 -------------------------------------------- groupedBy type number: 1 2 3 4 42 boolean: true false string: a b c function: function: 0x5590b5e69e00 function: 0x7fe95625b6b0
10. Rozdíl mezi funkcemi each a eachi
Existuje i funkce nazvaná eachi (s „i“ na konci jména). Tato funkce opět prochází tabulkou, ovšem zpracovává pouze ty prvky slovníku, jejichž klíčem je celé číslo. Čísla (kupodivu) nemusí tvořit ucelenou řadu začínající jedničkou, jak je to obvykle vyžadováno při iteraci s využitím programové smyčky typu for-each. Podívejme se nyní na funkční příklad:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- tisk tabulky, jejíž prvky tvoří pole function printTable(table) -- tisk prvků tabulky funkcí each each(table, function(values, key) -- tisk klíče io.write(key .. ":\t") -- tisk hodnot eachi(values, function (value) io.write(tostring(value) .. " ") end) print() end ) end -- oddělení obsahu function printSeparator() print("--------------------------------------------") end -- původní sekvence a = {1, 2, 3, 4, "a", "b", "c", nil, true, false, 42, function () end, print} -- tisk původní sekvence funkcí eachi printSeparator() print("original array") eachi(a, function(value, index) print(value) end) print() -- rozdělení prvků v sekvenci podle jejich typu g = groupBy(a, type) printSeparator() print("groupedBy type") printTable(g)
Výsledky:
-------------------------------------------- original array 1 2 3 4 a b c true false 42 function: 0x5620cc0b2e00 function: 0x7efffb42e6b0 -------------------------------------------- groupedBy type function: function: 0x5620cc0b2e00 function: 0x7efffb42e6b0 number: 1 2 3 4 42 boolean: true false string: a b c
Naproti tomu není možné slepě nahradit každé volání each za eachi, protože by se neprocházely ty prvky ve slovníku, které používají například řetězce jako klíče. Druhý demonstrační příklad tedy nevypíše žádné použitelné výsledky:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- tisk tabulky, jejíž prvky tvoří pole function printTable(table) -- tisk tabulky funkcí eachi eachi(table, function(values, key) -- tisk klíče io.write(key .. ":\t") -- tisk hodnot eachi(values, function (value) io.write(tostring(value) .. " ") end) print() end ) end -- oddělení obsahu function printSeparator() print("--------------------------------------------") end -- původní sekvence a = {1, 2, 3, 4, "a", "b", "c", nil, true, false, 42, function () end, print} -- tisk původní sekvence funkcí eachi printSeparator() print("original array") eachi(a, function(value, index) print(value) end) print() -- rozdělení prvků v sekvenci podle jejich typu g = groupBy(a, type) printSeparator() print("groupedBy type") printTable(g)
Výsledky nejsou v tomto případě zobrazeny:
-------------------------------------------- original array 1 2 3 4 a b c true false 42 function: 0x55ea586d1e00 function: 0x7f31fea176b0 -------------------------------------------- groupedBy type
11. Další funkce vyššího řádu procházející tabulkou: map
Funkci vyššího řádu map již poměrně dobře známe z knihovny Lua Fun, takže si bez dalších složitějších popisů ukažme způsob jejího použití. Nejprve aplikujeme tuto funkci na tabulku s více sekvencemi celočíselných hodnot s aplikací funkce product, která každou sekvenci nahradí součinem prvků:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- výpočet faktoriálu vstupních hodnot 1 až 10 n = range(1, 10) -- pomocné pole se sekvencí r = map(n, function (n) return range(1, n) end) -- výpočet produktu hodnot 1..n == n! f = map(r, product) -- tisk výsledků funkcí each each(f, function(x,y) print(y,x) end)
Výsledkem je (opět) tabulka faktoriálů hodnot:
1 1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880 10 3628800
Pokud vás zajímá, jak se k výsledku došlo, je možné si nechat zobrazit tabulky s mezivýsledky (tj. s vlastními sekvencemi hodnot):
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- tisk obsahu pole function printArrayInLine(array) -- test na prázdné pole if #array == 0 then print("*empty*") return end for i, value in ipairs(array) do io.write(value) if i ~= #array then io.write(", ") end end print() end -- výpočet faktoriálu vstupních hodnot 1 až 10 n = range(1, 10) -- pomocné pole se sekvencí r = map(n, function (n) return range(1, n) end) -- tisk tabulky tabulek pro ladicí účely each(r, printArrayInLine) -- výpočet produktu hodnot 1..n == n! f = map(r, product) -- tisk výsledků funkcí each each(f, function(x,y) print(y,x) end)
Nyní se po spuštění takto upraveného příkladu zobrazí i všechny mezivýsledky:
1 1, 2 1, 2, 3 1, 2, 3, 4 1, 2, 3, 4, 5 1, 2, 3, 4, 5, 6 1, 2, 3, 4, 5, 6, 7 1, 2, 3, 4, 5, 6, 7, 8 1, 2, 3, 4, 5, 6, 7, 8, 9 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 1 1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880 10 3628800
12. Zřetězení operací nad tabulkou s využitím chain
Nyní již nastal správný okamžik pro popis zřetězení většího množství operací nad tabulkou. Mnohdy se totiž s obsahem tabulky postupně provádí větší množství operací – mapování funkce, redukce, filtrace, otočení prvků, další filtrace prvků atd. A tyto operace je možné zapsat buď tak, že se hodnoty postupně předávají do dalších funkcí:
a1 = reverse(range(10))
popř.:
a2 = reverse(filter(range(10), function (x) return x%2==0 end))
Ovšem to není příliš čitelné. Výhodnější je použít přístup známý z mnoha programovacích jazyků podporujících datové streamy, tj. použití zřetězení. To má v případě knihovny Moses následující podobu:
-- první pole začínající standardně od jedničky -- je ihned po konstrukci otočeno a1 = M.chain(range(10)):reverse():value()
popř. pro tři operace (včetně filtrace):
-- nyní zkusíme zřetězit více operací a2 = M.chain(range(10)):filter(function (x) return x%2==0 end):reverse():value()
Z obou zápisů je patrné, že zřetězení začíná voláním M.chain() a že se jedná o metodu (i ostatní volání jsou metodami, tj. explicitně se neuvádí jejich první parametr self). Úplný zdrojový kód demonstračního příkladu s dvojicí zřetězení vypadá následovně:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- tisk obsahu pole function printArray(array) for index, value in ipairs(array) do print(index, value) end end -- oddělení obsahu function printSeparator() print("-------------------------------") end -- první pole začínající standardně od jedničky -- je ihned po konstrukci otočeno a1 = M.chain(range(10)):reverse():value() printSeparator() print("reverse(range(10))") printArray(a1) -- nyní zkusíme zřetězit více operací a2 = M.chain(range(10)):filter(function (x) return x%2==0 end):reverse():value() printSeparator() print("reverse(filter(range(10)))") printArray(a2)
A vypsané výsledky:
------------------------------- reverse(range(10)) 1 10 2 9 3 8 4 7 5 6 6 5 7 4 8 3 9 2 10 1 ------------------------------- reverse(filter(range(10))) 1 10 2 8 3 6 4 4 5 2
13. Složitější příklady zřetězení operací
Nyní, když již známe způsob zajištění zřetězení operací, se můžeme pokusit o zápis výpočtu tabulky faktoriálů hodnot 1 až 10 na dva řádky. První řádek zajistí:
- Vygenerování vstupů 1 až 10
- Vytvoření rozsahu (range) pro každý vstup 1 až 10
- Výpočet produktu (součinu) všech hodnot rozsahu
- Převod výsledků zpět na běžné pole
Druhý programový řádek již jen použije funkci each pro tisk obsahu pole na standardní výstup (s tím, že index současně odpovídá vstupní hodnotě n):
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- výpočet faktoriálu vstupních hodnot 1 až 10 fact = M.chain(range(1, 10)):map(function (n) return range(1, n) end):map(product):value() each(fact, function(x,y) print(y,x) end)
Výsledkem je skutečně tabulka faktoriálů:
1 1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880 10 3628800
Ovšem nic nám nezabrání zapsat celý výpočet na jediný řádek, protože i volání funkce vyššího řádu each může být zřetězeno. Zde ovšem již nemusíme na konci volat metodu value(), protože žádné výsledné pole nepotřebujeme (jeho obsah již byl vypsán):
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- výpočet faktoriálu vstupních hodnot 1 až 10 fact = M.chain(range(1, 10)):map(function (n) return range(1, n) end):map(product):each(function(x,y) print(y,x) end)
Tabulka s výsledky by měla vypadat stejně, jako v předchozím případě:
1 1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880 10 3628800
14. Funkce vyššího řádu mapReduce
Zajímavá je i další funkce vyššího řádu, která se nazývá mapReduce. Tato funkce provádí operaci reduce, kterou již známe, ovšem postupně vytvářené (akumulované) mezivýsledky jsou ukládány do nově vytvářeného pole. Toto pole tedy bude nakonec obsahovat historii všech naakumulovaných hodnot. Popsanou operaci si můžeme otestovat na dalším demonstračním příkladu, který s využitím mapReduce vytvoří historii postupného výpočtu faktoriálu hodnoty 10 a tedy automaticky vytvoří i tabulku s faktoriály pro vstupy 1 až 10:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- konstrukce pole a = range(1, 10) -- výpočet produktu hodnot 1..n == n! s uložením všech mezivýsledků -- (použila se anonymní funkce) prod = mapReduce(a, function (x, y) return x * y end) -- tisk produktu hodnot 1..n == n! each(prod, function(x,y) print(y,x) end)
Výsledné pole po výpisu skutečně zobrazí tabulku faktoriálu pro vstupy 1 až 10:
1 1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880 10 3628800
15. Rozdíl mezi funkcemi mapReduce a mapReduceRight
Podobně jako existuje dvojice funkcí reduce a reduceRight, je k funkci mapReduce přidána analogická funkce nazvaná mapReduceRigth. I tato funkce vytváří pole s postupně akumulovanými mezivýsledky, ovšem vstupní pole je procházeno odzadu. Výpočet faktoriálu tedy můžeme provést prakticky stejným způsobem, jaký jsme viděli v předchozí kapitole, ovšem mezivýsledky již nebudou tvořit tabulku faktoriálů předchozích hodnot:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- konstrukce pole a = range(1, 10) -- výpočet produktu hodnot 1..n == n! s uložením všech mezivýsledků -- (použila se anonymní funkce) prod = mapReduceRight(a, function (x, y) return x * y end) -- tisk produktu hodnot 1..n == n! each(prod, function(x,y) print(y,x) end)
Tabulka s výsledky (jen poslední je správný):
1 10 2 90 3 720 4 5040 5 30240 6 151200 7 604800 8 1814400 9 3628800 10 3628800
16. Iterace prvky tabulky funkcemi sortedk a sortedv
V případě, že máme tabulku se slovníkem, je při běžném tisku ve smyčce typu for-each použito „náhodné“ pořadí prvků (ve skutečnosti není náhodné, ale závisí na hešovací funkci atd.). Ovšem mnohdy potřebujeme prvky vytisknout v takovém pořadí, jaké odpovídá seřazeným klíčům. A právě k tomuto účelu slouží funkce sorted, viz následující demonstrační příklad:
-- načtení knihovny Moses a současně import symbolů do globálního jmenného prostoru M = require "moses" M.import() -- oddělení obsahu function printSeparator() print("-------------------------------") end a = {} -- naplnění pole for i = 1, 9 do a[tostring(i)] = i end printSeparator() -- tisk pole for key, value in pairs(a) do print(key, value) end printSeparator() -- tisk pole setříděného podle klíčů for key, value in sortedk(a) do print(key, value) end
Z výsledků je patrné, že v prvním výpisu je pořadí z pohledu uživatele náhodné, kdežto ve druhém případě jsou prvky seřazeny podle klíčů (klíče jsou řetězci):
------------------------------- 5 5 4 4 3 3 2 2 9 9 8 8 7 7 6 6 1 1 ------------------------------- 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9
Setřídění podle hodnot (poslední smyčka):
a = {} -- naplnění pole for i = 1, 9 do a[tostring(i)] = 1/i end printSeparator() -- tisk pole for key, value in pairs(a) do print(key, value) end printSeparator() -- tisk pole setříděného podle klíčů for key, value in sortedk(a) do print(key, value) end printSeparator() -- tisk pole setříděného podle hodnot for key, value in sortedv(a) do print(key, value) end
Výsledky:
------------------------------- 9 0.11111111111111 8 0.125 3 0.33333333333333 2 0.5 1 1.0 7 0.14285714285714 6 0.16666666666667 5 0.2 4 0.25 ------------------------------- 1 1.0 2 0.5 3 0.33333333333333 4 0.25 5 0.2 6 0.16666666666667 7 0.14285714285714 8 0.125 9 0.11111111111111 ------------------------------- 9 0.11111111111111 8 0.125 7 0.14285714285714 6 0.16666666666667 5 0.2 4 0.25 3 0.33333333333333 2 0.5 1 1.0
17. Obsah závěrečného článku o knihovně Moses
Knihovna Moses obsahuje mnohem větší množství funkcionality než již dříve popsaná knihovna Lua Fun. Z tohoto důvodu se knihovně Moses budeme věnovat ještě v jednom článku. Popíšeme si především dvě zajímavé funkcionální techniky nazvané memoization a currying (poslední technika byla pojmenována podle Haskella Curryho, jehož jméno není ve funkcionálním světě neznámé). Taktéž se zmíníme u funkcích typu „path“, které umožňují získat data z vnořených tabulek. Může se jednat například o deserializovaná data získaná z formátů XML, JSON či EDN. Tyto funkce, konkrétně se jedná o funkce path, spreadPath a flattenPath, částečně vychází z technologie XPath, ovšem jsou pojaty uživatelsky přívětivějším způsobem (minimálně pro vývojáře, kteří XPath a jeho DSL neznají či aktivně nepoužívají).
18. Repositář s demonstračními příklady
Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do nového Git repositáře, který je dostupný na adrese https://github.com/tisnik/functional-lua (jedná se o podadresář moses/). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně několik jednotek kilobajtů), můžete namísto toho použít odkazy na jednotlivé demonstrační příklady a jejich části, které naleznete v následující tabulce:
19. Články o programovacím jazyce Lua i o technologii LuaJITu
Předchozí dva články o funkcionálním stylu programování podporovaného knihovnou Lua Fun a úvodní článek o knihovně Moses:
- Lua Fun: knihovna pro zpracování konečných i nekonečných sekvencí v jazyce Lua
https://www.root.cz/clanky/lua-fun-knihovna-pro-zpracovani-konecnych-i-nekonecnych-sekvenci-v-jazyce-lua/ - Lua Fun: knihovna pro zpracování konečných i nekonečných sekvencí v jazyce Lua (dokončení)
https://www.root.cz/clanky/lua-fun-knihovna-pro-zpracovani-konecnych-i-nekonecnych-sekvenci-v-jazyce-lua-dokonceni/ - Podpora funkcionálního programovaní v jazyku Lua s využitím knihovny Moses
https://www.root.cz/clanky/podpora-funkcionalniho-programovani-v-jazyku-lua-s-vyuzitim-knihovny-moses/
Programovacím jazykem Lua jsme se již na stránkách Rootu poměrně podrobně zabývali v samostatném seriálu. Jedná se o snadno naučitelný jazyk, který je ovšem (mj. i díky konceptu metatabulek) rozšiřitelný a poměrně tvárný. Viz též následující odkazy na již vydané články (včetně odkazu na e-book, jenž na základě těchto článků později vznikl):
- Programovací jazyk Lua
https://www.root.cz/clanky/programovaci-jazyk-lua/ - Základní konstrukce v programovacím jazyku Lua
https://www.root.cz/clanky/zakladni-konstrukce-v-programovacim-jazyku-lua/ - Operátory a asociativní pole v jazyku Lua
https://www.root.cz/clanky/operatory-a-asociativni-pole-v-jazyku-lua/ - Funkce v programovacím jazyku Lua
https://www.root.cz/clanky/funkce-v-programovacim-jazyku-lua/ - Funkce v programovacím jazyku Lua – uzávěry
https://www.root.cz/clanky/funkce-v-programovacim-jazyku-lua-uzavery/ - Programovací jazyk Lua vestavěný do aplikací
https://www.root.cz/clanky/programovaci-jazyk-lua-vestaveny-do-aplikaci/ - Programovací jazyk Lua v aplikacích II
https://www.root.cz/clanky/programovaci-jazyk-lua-v-aplikacich-ii/ - Objektově orientované programování v Lua
https://www.root.cz/clanky/objektove-orientovane-programovani-v-lua/ - Objektově orientované programování v Lua II
https://www.root.cz/clanky/objektove-orientovane-programovani-v-lua-ii/ - Programovací jazyk Lua a koprogramy
https://www.root.cz/clanky/programovaci-jazyk-lua-a-koprogramy/ - Knihovny a frameworky pro programovací jazyk Lua
https://www.root.cz/clanky/knihovny-a-frameworky-pro-programovaci-jazyk-lua/ - Lua + LÖVE: vytvořte si vlastní hru
https://www.root.cz/clanky/lua-love-vytvorte-si-vlastni-hru/ - Hrátky se systémem LÖVE
https://www.root.cz/clanky/hratky-se-systemem-love/ - Vytváříme hru v systému LÖVE
https://www.root.cz/clanky/vytvarime-hru-v-systemu-love/ - Hrátky se systémem LÖVE – částicové systémy
https://www.root.cz/clanky/hratky-se-systemem-love-casticove-systemy/ - Hrátky se systémem LÖVE – kolize a odrazy těles
https://www.root.cz/clanky/hratky-se-systemem-love-ndash-kolize-a-odrazy-teles/ - Hrátky se systémem LÖVE – kolize a odrazy těles II
https://www.root.cz/clanky/hratky-se-systemem-love-kolize-a-odrazy-teles-ii/ - Hrátky se systémem LÖVE – pružné vazby mezi tělesy
https://www.root.cz/clanky/hratky-se-systemem-love-pruzne-vazby-mezi-telesy/ - Hrátky se systémem LÖVE – dokončení
https://www.root.cz/clanky/hratky-se-systemem-love-dokonceni/ - LuaJ – implementace jazyka Lua v Javě
https://www.root.cz/clanky/luaj-ndash-implementace-jazyka-lua-v-jave/ - LuaJ a skriptování podle specifikace JSR-223
https://www.root.cz/clanky/luaj-a-skriptovani-podle-specifikace-jsr-223/ - Metalua: programovatelné rozšíření sémantiky jazyka Lua
https://www.root.cz/clanky/metalua-programovatelne-rozsireni-semantiky-jazyka-lua/ - Metalua: užitečná rozšíření jazyka Lua
https://www.root.cz/clanky/metalua-uzitecna-rozsireni-jazyka-lua/ - Programovací jazyk Lua v roli skriptovacího jazyka pro WWW stránky
https://www.root.cz/clanky/programovaci-jazyk-lua-v-roli-skriptovaciho-jazyka-pro-www-stranky/ - Interpretry, překladače, JIT překladače a transpřekladače programovacího jazyka Lua
https://www.root.cz/clanky/interpretry-prekladace-jit-prekladace-a-transprekladace-programovaciho-jazyka-lua/ - Kooperace mezi jazykem Lua a nativním (céčkovým) kódem
https://www.root.cz/clanky/kooperace-mezi-jazykem-lua-a-nativnim-ceckovym-kodem/ - Kooperace mezi jazykem Lua a nativním (céčkovým) kódem: knihovna FFI
https://www.root.cz/clanky/kooperace-mezi-jazykem-lua-a-nativnim-ceckovym-kodem-knihovna-ffi/ - Profilery pro programovací jazyk Lua
https://www.root.cz/clanky/profilery-pro-programovaci-jazyk-lua/ - Využití knihovny debug v programovacím jazyku Lua
https://www.root.cz/clanky/vyuziti-knihovny-debug-v-programovacim-jazyku-lua/ - Programovací jazyk Lua (e-book)
https://www.knihydobrovsky.cz/e-kniha/programovaci-jazyk-lua-240253190
Původně byla Lua realizována jako klasický interpret – prováděl se automatický a prakticky okamžitý překlad do bajtkódu, který byl následně interpretován. Později byl vytvořen i plnohodnotný (a nutno podotknout, že až neobvykle dobrý) just-in-time (JIT) překladač nazvaný LuaJIT. Touto zajímavou technologií jsme se zabývali v následující sérii článků (které jsou poněkud paradoxně součástí seriálu o programovacím jazyku Java a JVM):
- LuaJIT – Just in Time překladač pro programovací jazyk Lua
https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua (2)
https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-2/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua (3)
https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-3/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua (4)
https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-4/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua (5 – tabulky a pole)
https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-5-tabulky-a-pole/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua (6 – překlad programových smyček do mezijazyka LuaJITu)
https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-6-preklad-programovych-smycek-do-mezijazyka-luajitu/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua (7 – dokončení popisu mezijazyka LuaJITu)
https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-7-dokonceni-popisu-mezijazyka-luajitu/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua (8 – základní vlastnosti trasovacího JITu)
https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-8-zakladni-vlastnosti-trasovaciho-jitu/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua (9 – další vlastnosti trasovacího JITu)
https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-9-dalsi-vlastnosti-trasovaciho-jitu/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua (10 – JIT překlad do nativního kódu)
https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-10-jit-preklad-do-nativniho-kodu/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua (11 – JIT překlad do nativního kódu procesorů s architekturami x86 a ARM)
https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-11-jit-preklad-do-nativniho-kodu-procesoru-s-architekturami-x86-a-arm/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua (12 – překlad operací s reálnými čísly)
https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-12-preklad-operaci-s-realnymi-cisly/
A konečně nesmíme zapomenout na to, že kromě původní implementace interpretru a LuaJITu existuje celá řada dalších implementací tohoto programovacího jazyka. Některé z těchto implementací byly zmíněny v následujících článcích:
- Skriptovací jazyk Lua v aplikacích naprogramovaných v Go
https://www.root.cz/clanky/skriptovaci-jazyk-lua-v-aplikacich-naprogramovanych-v-go/ - Programovací jazyk Lua v roli skriptovacího jazyka pro WWW stránky
https://www.root.cz/clanky/programovaci-jazyk-lua-v-roli-skriptovaciho-jazyka-pro-www-stranky/ - LuaJ – implementace jazyka Lua v Javě
https://www.root.cz/clanky/luaj-ndash-implementace-jazyka-lua-v-jave/ - Tvorba pluginů pro Vim s využitím programovacího jazyka Lua
https://www.root.cz/clanky/tvorba-pluginu-pro-vim-s-vyuzitim-programovaciho-jazyka-lua/
20. Odkazy na Internetu
- Repositář projektu Lua Fun
https://github.com/luafun/luafun - Lua Functional 0.1.3 documentation
https://luafun.github.io/reference.html - Getting Started
https://luafun.github.io/getting_started.html - Rockspec knihovny Fun
https://raw.githubusercontent.com/luafun/luafun/master/fun-scm-1.rockspec - Awesome Lua – A curated list of quality Lua packages and resources.
https://github.com/LewisJEllis/awesome-lua - Repositář projektu Moses
https://github.com/Yonaba/Moses/ - Lambda the Ultimate: Coroutines in Lua
http://lambda-the-ultimate.org/node/438 - Coroutines Tutorial
http://lua-users.org/wiki/CoroutinesTutorial - Lua Coroutines Versus Python Generators
http://lua-users.org/wiki/LuaCoroutinesVersusPythonGenerators - Programming in Lua 9.1 – Coroutine Basics
http://www.lua.org/pil/9.1.html - Wikipedia CZ: Koprogram
http://cs.wikipedia.org/wiki/Koprogram - Wikipedia EN: Coroutine
http://en.wikipedia.org/wiki/Coroutine - Repositář knihovny Moses
https://github.com/Yonaba/Moses/ - Návod k použití knihovny Moses
https://github.com/Yonaba/Moses/blob/master/doc/tutorial.md - How to understand clojure's lazy-seq
https://stackoverflow.com/questions/44095400/how-to-understand-clojures-lazy-seq - Lua Implementations
http://lua-users.org/wiki/LuaImplementations - Generator (computer programming)
https://en.wikipedia.org/wiki/Generator_(computer_programming) - Lambda the Ultimate: Coroutines in Lua,
http://lambda-the-ultimate.org/node/438 - Coroutines Tutorial,
http://lua-users.org/wiki/CoroutinesTutorial - Lua Coroutines Versus Python Generators,
http://lua-users.org/wiki/LuaCoroutinesVersusPythonGenerators - Category:Lua na Rosetta code
https://rosettacode.org/wiki/Category:Lua - Programming in Lua: 23 – The Debug Library
http://www.lua.org/pil/23.html - Programming in Lua: 23.1 – Introspective Facilities
http://www.lua.org/pil/23.1.html - Programming in Lua: 23.2 – Hooks
http://www.lua.org/pil/23.2.html - Lua 5.2 Reference Manual: 6.10 – The Debug Library
http://www.lua.org/manual/5.2/manual.html#6.10 - Turtles all the way down
https://en.wikipedia.org/wiki/Turtles_all_the_way_down - Issues k projektu LuaFun
https://github.com/luafun/luafun/issues - Archived | Embed Lua for scriptable apps
https://developer.ibm.com/tutorials/l-embed-lua/ - Embedding Lua
https://www.oreilly.com/library/view/lua-quick-start/9781789343229/3a6f3daf-f74c-4a25-a125–584da58568e4.xhtml