Hlavní navigace

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 zabývat funkcemi, které jsou určeny pro zpracování tabulek. Oproti knihovně Lua Fun je nabídka operací implementovaná v knihovně Moses mnohem rozmanitější, proto si ukážeme i více příkladů.
Pavel Tišnovský 4. 8. 2020
Doba čtení: 41 minut

Sdílet

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

20. Odkazy na Internetu

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
Poznámka: ve skutečnosti může tabulka v jazyku Lua obsahovat dvě části – „seznamovou“ a „slovníkovou“. Jedná se o poměrně specifický rys tohoto programovacího jazyka, protože v mnoha jiných jazycích jsou k dispozici dvě odlišné datové struktury většinou i s odlišnými operacemi. V programovacím jazyku Lua je situace o to více matoucí, že některé operace – typicky operace, resp. operátor pro zjištění délky struktury – pracují pouze se „seznamovou“ částí tabulky. Ovšem díky použití knihovny Moses můžeme být od těchto nízkoúrovňových detailů do značné míry odstíněni. Více viz například klasický výklad dostupný na adrese http://lua-users.org/wiki/TablesTutorial.

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
Poznámka: s různými způsoby výpočtu faktoriálu se ještě několikrát setkáme v navazujících kapitolách.

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
Poznámka: v praxi tedy bude nutné testovat prázdná pole na vstupu.

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
Poznámka: v tomto demonstračním příkladu jsme narazili na jednu velmi problematickou vlastnost programovacího jazyka Lua – výpis obsahu tabulky (resp. iterace přes její prvky) se zastaví na prvním prvku s hodnotou nil.

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
Poznámka: ve skutečnosti není nutné používat funkci tostring v případě, že prvky pole jsou celá čísla. Konverze čísla na řetězec se v tomto případě (logicky) provede automaticky. A protože Lua rozlišuje mezi operátorem „+“ a „..“, nedochází k nejasnostem, jaká operace se vlastně provádí:
-- 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
Poznámka: metoda value() převede objekt zpět na běžnou tabulku.

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

  1. Vygenerování vstupů 1 až 10
  2. Vytvoření rozsahu (range) pro každý vstup 1 až 10
  3. Výpočet produktu (součinu) všech hodnot rozsahu
  4. 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:

# Příklad Stručný popis Cesta
1 21_sum_prod.lua výpočet součtu a součinu prvků pole https://github.com/tisnik/functional-lua/tree/master/moses/21_sum_prod.lua
2 22_sum_prod_empty_array.lua výpočet součtu a součinu prvků prázdného pole https://github.com/tisnik/functional-lua/tree/master/moses/22_sum_prod_em­pty_array.lua
3 23_factorial.lua zobrazení faktoriálu vstupních hodnot 1..10 https://github.com/tisnik/functional-lua/tree/master/moses/23_factorial.lua
4 24_mean_median_min_max.lua výpočet průměru, mediánu, minimální a maximální hodnoty https://github.com/tisnik/functional-lua/tree/master/moses/24_me­an_median_min_max.lua
5 25_mean_median_min_max.lua výpočet průměru, mediánu, minimální a maximální hodnoty https://github.com/tisnik/functional-lua/tree/master/moses/25_me­an_median_min_max.lua
6 26_mean_median_min_max_zeros.lua výpočet průměru, mediánu, minimální a maximální hodnoty prázdného pole https://github.com/tisnik/functional-lua/tree/master/moses/26_me­an_median_min_max_zeros.lua
7 27_flatten.lua „zploštění“ pole na všech úrovních https://github.com/tisnik/functional-lua/tree/master/moses/27_flatten.lua
8 28_flatten_shallow.lua první iterace „zploštění“ pole https://github.com/tisnik/functional-lua/tree/master/moses/28_flat­ten_shallow.lua
9 29_flatten_steps.lua postupné iterace zploštění pole https://github.com/tisnik/functional-lua/tree/master/moses/29_flat­ten_steps.lua
10 30_compact.lua odstranění prvků false a nil ze vstupního pole https://github.com/tisnik/functional-lua/tree/master/moses/30_compact.lua
11 31_xprod.lua vytvoření tabulky se všemi kombinacemi prvků ze vstupních polí https://github.com/tisnik/functional-lua/tree/master/moses/31_xprod.lua
12 32_xprod_empty_array.lua vytvoření tabulky se všemi kombinacemi prvků ze vstupních polí, jedno z polí je prázdné https://github.com/tisnik/functional-lua/tree/master/moses/32_xprod_em­pty_array.lua
13 33_reduce.lua procházení tabulkou s akumulací výsledků https://github.com/tisnik/functional-lua/tree/master/moses/33_reduce.lua
14 34_reduce_anonymous_function.lua podobný příklad, ovšem založený na anonymní funkci https://github.com/tisnik/functional-lua/tree/master/moses/34_re­duce_anonymous_function.lua
15 35_reduce_concat.lua spojení všech prvků do jediného řetězce https://github.com/tisnik/functional-lua/tree/master/moses/35_re­duce_concat.lua
16 36_reduce_right.lua rozdíl mezi funkcemi reduce a reduceRight https://github.com/tisnik/functional-lua/tree/master/moses/36_re­duce_right.lua
17 37_group_by.lua rozdělení prvků tabulky do skupin na základě kritéria https://github.com/tisnik/functional-lua/tree/master/moses/37_group_by.lua
18 38_group_by_type.lua rozdělení prvků tabulky do skupin na základě jejich typu https://github.com/tisnik/functional-lua/tree/master/moses/38_grou­p_by_type.lua
19 39_each.lua Iterace nad prvky tabulky funkcí each https://github.com/tisnik/functional-lua/tree/master/moses/39_each.lua
20 40_each_no_loops.lua Náhrada programových smyček za funkci each https://github.com/tisnik/functional-lua/tree/master/moses/40_e­ach_no_loops.lua
21 41_eachi.lua Rozdíly mezi funkcemi each a eachi https://github.com/tisnik/functional-lua/tree/master/moses/41_eachi.lua
22 42_eachi_bad_usage.lua Špatné použití funkce eachi https://github.com/tisnik/functional-lua/tree/master/moses/42_e­achi_bad_usage.lua
23 43_map.lua Funkce vyššího řádu map https://github.com/tisnik/functional-lua/tree/master/moses/43_map.lua
24 44_map.lua Stejný příklad, ovšem s ladicím výpisem https://github.com/tisnik/functional-lua/tree/master/moses/44_map.lua
25 45_chain.lua Zřetězení více operací nad tabulkou funkcí chain https://github.com/tisnik/functional-lua/tree/master/moses/45_chain.lua
26 46_chain_map.lua Výpočet tabulky faktoriálů na dvou řádcích https://github.com/tisnik/functional-lua/tree/master/moses/46_chain_map.lua
27 47_chain_map_each.lua Výpočet tabulky faktoriálů na jediném řádku https://github.com/tisnik/functional-lua/tree/master/moses/47_cha­in_map_each.lua
28 48_map_reduce.lua Ukázka použití funkce mapReduce https://github.com/tisnik/functional-lua/tree/master/moses/48_map_re­duce.lua
29 49_map_reduce_right.lua Rozdíl mezi funkcí mapReduce a mapReduceRight https://github.com/tisnik/functional-lua/tree/master/moses/49_map_re­duce_right.lua
30 50_sortedk.lua Příklad použití funkce sortedk https://github.com/tisnik/functional-lua/tree/master/moses/50_sortedk.lua

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:

  1. 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/
  2. 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/
  3. 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):

Linuxová školení

  1. Programovací jazyk Lua
    https://www.root.cz/clanky/pro­gramovaci-jazyk-lua/
  2. Základní konstrukce v programovacím jazyku Lua
    https://www.root.cz/clanky/zakladni-konstrukce-v-programovacim-jazyku-lua/
  3. Operátory a asociativní pole v jazyku Lua
    https://www.root.cz/clanky/operatory-a-asociativni-pole-v-jazyku-lua/
  4. Funkce v programovacím jazyku Lua
    https://www.root.cz/clanky/funkce-v-programovacim-jazyku-lua/
  5. Funkce v programovacím jazyku Lua – uzávěry
    https://www.root.cz/clanky/funkce-v-programovacim-jazyku-lua-uzavery/
  6. Programovací jazyk Lua vestavěný do aplikací
    https://www.root.cz/clanky/pro­gramovaci-jazyk-lua-vestaveny-do-aplikaci/
  7. Programovací jazyk Lua v aplikacích II
    https://www.root.cz/clanky/pro­gramovaci-jazyk-lua-v-aplikacich-ii/
  8. Objektově orientované programování v Lua
    https://www.root.cz/clanky/objektove-orientovane-programovani-v-lua/
  9. Objektově orientované programování v Lua II
    https://www.root.cz/clanky/objektove-orientovane-programovani-v-lua-ii/
  10. Programovací jazyk Lua a koprogramy
    https://www.root.cz/clanky/pro­gramovaci-jazyk-lua-a-koprogramy/
  11. Knihovny a frameworky pro programovací jazyk Lua
    https://www.root.cz/clanky/knihovny-a-frameworky-pro-programovaci-jazyk-lua/
  12. Lua + LÖVE: vytvořte si vlastní hru
    https://www.root.cz/clanky/lua-love-vytvorte-si-vlastni-hru/
  13. Hrátky se systémem LÖVE
    https://www.root.cz/clanky/hratky-se-systemem-love/
  14. Vytváříme hru v systému LÖVE
    https://www.root.cz/clanky/vytvarime-hru-v-systemu-love/
  15. Hrátky se systémem LÖVE – částicové systémy
    https://www.root.cz/clanky/hratky-se-systemem-love-casticove-systemy/
  16. 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/
  17. 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/
  18. 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/
  19. Hrátky se systémem LÖVE – dokončení
    https://www.root.cz/clanky/hratky-se-systemem-love-dokonceni/
  20. LuaJ – implementace jazyka Lua v Javě
    https://www.root.cz/clanky/luaj-ndash-implementace-jazyka-lua-v-jave/
  21. LuaJ a skriptování podle specifikace JSR-223
    https://www.root.cz/clanky/luaj-a-skriptovani-podle-specifikace-jsr-223/
  22. Metalua: programovatelné rozšíření sémantiky jazyka Lua
    https://www.root.cz/clanky/metalua-programovatelne-rozsireni-semantiky-jazyka-lua/
  23. Metalua: užitečná rozšíření jazyka Lua
    https://www.root.cz/clanky/metalua-uzitecna-rozsireni-jazyka-lua/
  24. Programovací jazyk Lua v roli skriptovacího jazyka pro WWW stránky
    https://www.root.cz/clanky/pro­gramovaci-jazyk-lua-v-roli-skriptovaciho-jazyka-pro-www-stranky/
  25. 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/
  26. 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/
  27. 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/
  28. Profilery pro programovací jazyk Lua
    https://www.root.cz/clanky/profilery-pro-programovaci-jazyk-lua/
  29. Využití knihovny debug v programovacím jazyku Lua
    https://www.root.cz/clanky/vyuziti-knihovny-debug-v-programovacim-jazyku-lua/
  30. 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):

  1. LuaJIT – Just in Time překladač pro programovací jazyk Lua
    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 (2)
    https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-2/
  3. 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/
  4. 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/
  5. 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/
  6. 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/
  7. 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/
  8. 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/
  9. 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/
  10. 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/
  11. 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/
  12. 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:

  1. Skriptovací jazyk Lua v aplikacích naprogramovaných v Go
    https://www.root.cz/clanky/skriptovaci-jazyk-lua-v-aplikacich-naprogramovanych-v-go/
  2. Programovací jazyk Lua v roli skriptovacího jazyka pro WWW stránky
    https://www.root.cz/clanky/pro­gramovaci-jazyk-lua-v-roli-skriptovaciho-jazyka-pro-www-stranky/
  3. LuaJ – implementace jazyka Lua v Javě
    https://www.root.cz/clanky/luaj-ndash-implementace-jazyka-lua-v-jave/
  4. 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/
Poznámka: předchozí články sice nepokrývají ekosystém tohoto jazyka dokonale, ovšem přibližně 90% všech relevantních informací je uvedeno. To je v případě dalších ekosystémů (Java, Python, Ruby, …) prakticky nemožné, resp. to není v silách jediného autora :-)

20. Odkazy na Internetu

  1. Repositář projektu Lua Fun
    https://github.com/luafun/luafun
  2. Lua Functional 0.1.3 documentation
    https://luafun.github.io/re­ference.html
  3. Getting Started
    https://luafun.github.io/get­ting_started.html
  4. Rockspec knihovny Fun
    https://raw.githubusercon­tent.com/luafun/luafun/mas­ter/fun-scm-1.rockspec
  5. Awesome Lua – A curated list of quality Lua packages and resources.
    https://github.com/LewisJEllis/awesome-lua
  6. Repositář projektu Moses
    https://github.com/Yonaba/Moses/
  7. Lambda the Ultimate: Coroutines in Lua
    http://lambda-the-ultimate.org/node/438
  8. Coroutines Tutorial
    http://lua-users.org/wiki/CoroutinesTutorial
  9. Lua Coroutines Versus Python Generators
    http://lua-users.org/wiki/LuaCorouti­nesVersusPythonGenerators
  10. Programming in Lua 9.1 – Coroutine Basics
    http://www.lua.org/pil/9.1.html
  11. Wikipedia CZ: Koprogram
    http://cs.wikipedia.org/wi­ki/Koprogram
  12. Wikipedia EN: Coroutine
    http://en.wikipedia.org/wi­ki/Coroutine
  13. Repositář knihovny Moses
    https://github.com/Yonaba/Moses/
  14. Návod k použití knihovny Moses
    https://github.com/Yonaba/Mo­ses/blob/master/doc/tutori­al.md
  15. How to understand clojure's lazy-seq
    https://stackoverflow.com/qu­estions/44095400/how-to-understand-clojures-lazy-seq
  16. Lua Implementations
    http://lua-users.org/wiki/LuaImplementations
  17. Generator (computer programming)
    https://en.wikipedia.org/wi­ki/Generator_(computer_pro­gramming)
  18. Lambda the Ultimate: Coroutines in Lua,
    http://lambda-the-ultimate.org/node/438
  19. Coroutines Tutorial,
    http://lua-users.org/wiki/CoroutinesTutorial
  20. Lua Coroutines Versus Python Generators,
    http://lua-users.org/wiki/LuaCorouti­nesVersusPythonGenerators
  21. Category:Lua na Rosetta code
    https://rosettacode.org/wi­ki/Category:Lua
  22. Programming in Lua: 23 – The Debug Library
    http://www.lua.org/pil/23.html
  23. Programming in Lua: 23.1 – Introspective Facilities
    http://www.lua.org/pil/23.1.html
  24. Programming in Lua: 23.2 – Hooks
    http://www.lua.org/pil/23.2.html
  25. Lua 5.2 Reference Manual: 6.10 – The Debug Library
    http://www.lua.org/manual/5­.2/manual.html#6.10
  26. Turtles all the way down
    https://en.wikipedia.org/wi­ki/Turtles_all_the_way_down
  27. Issues k projektu LuaFun
    https://github.com/luafun/lu­afun/issues
  28. Archived | Embed Lua for scriptable apps
    https://developer.ibm.com/tutorials/l-embed-lua/
  29. Embedding Lua
    https://www.oreilly.com/li­brary/view/lua-quick-start/9781789343229/3a6f3daf-f74c-4a25-a125–584da58568e4.xhtml