Hlavní navigace

Lua Fun: knihovna pro zpracování konečných i nekonečných sekvencí v jazyce Lua (dokončení)

23. 7. 2020
Doba čtení: 38 minut

Sdílet

Ve druhé části článku o knihovně Lua Fun dokončíme popis práce s nekonečnými sekvencemi a dále se budeme věnovat dalším užitečným funkcionálním technikám, které tato knihovna programátorům nabízí.

Obsah

1. Funkce nth a přístup k prvkům nekonečných sekvencí

2. Operace prováděné interně ve funkci nth

3. Chování funkce taken pro nekonečné sekvence

4. Chování funkce split pro nekonečné sekvence

5. Zazipování několika nekonečných sekvencí do nové sekvence

6. Kdy se tedy vyhodnocují prvky nekonečné sekvence získané funkcí zip?

7. Zjednodušení programů s využitím funkce each

8. Výběr prvků do nové sekvence funkcí vyššího řádu filter

9. Výběr prvků na základě aplikace predikátu match – výběr řetězců podle vzorku

10. Rozdělení prvků v sekvenci do dvou sekvencí funkcí partition

11. Funkce vyššího řádu map

12. Opakování sekvence funkcí/generátorem cycle

13. Kompozice sekvencí

14. Přidání indexů k prvkům s využitím funkce enumerate

15. Proložení prvků v sekvenci konstantními hodnotami

16. Funkce vyššího řádu foldl neboli reduce

17. Přehled funkcí deklarovaných v knihovně Lua Fun

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. Funkce nth a přístup k prvkům nekonečných sekvencí

Na úvodní článek o knihovně Lua Fun dnes navážeme a dokončíme popis této užitečné a prakticky použitelné knihovny, která do značné míry rozšiřuje možnosti programovacího jazyka Lua. Nejdříve se budeme věnovat nekonečným sekvencím, tj. sekvencím hodnot vytvářených nějakou funkcí (či přesněji řečeno generátorem). Dále si popíšeme implementaci „klasických“ operací, které nalezneme v prakticky všech funkcionálních programovacích jazycích. Jedná se o operace typu map, filter a reduce (tato funkce se někdy nazývá fold či přesněji foldl). I tyto operace knihovna Lua Fun pochopitelně plně podporuje.

Nejdříve si připomeňme existenci funkce nazvané nth. Tato funkce umožňuje, jak již ostatně její název naznačuje, přístup k n-tému prvku sekvence, přičemž v programovacím jazyku Lua indexujeme prvky od jedničky a nikoli od nuly (což má své nesporné výhody, ovšem způsobuje to problémy při přechodu z programovacího jazyka z céčkové větve). Zajímavé je, že funkce nth dokáže pracovat jak s konečnými sekvencemi (zde může mít konstantní složitost), tak i se sekvencemi nekonečnými, kde je její složitost O(n), protože je nutné k požadovanému prvku doiterovat. Ostatně je to patrné i ze způsobu implementace této funkce, který vypadá následovně:

local nth = function(n, gen_x, param_x, state_x)
    assert(n > 0, "invalid first argument to nth")
    -- An optimization for arrays and strings
    if gen_x == ipairs_gen then
        return param_x[n]
    elseif gen_x == string_gen then
        if n <= #param_x then
            return string.sub(param_x, n, n)
        else
            return nil
        end
    end
    for i=1,n-1,1 do
        state_x = gen_x(param_x, state_x)
        if state_x == nil then
            return nil
        end
    end
    return return_if_not_empty(gen_x(param_x, state_x))
end
Poznámka: povšimněte si, že jsou řešeny i stavy, kdy budeme chtít získat prvek, který již v sekvenci neexistuje.

Vzhledem k tomu, že nth umožňuje získat prvek s určitým indexem i z nekonečné sekvence, můžeme si toto chování ověřit na dnešním prvním demonstračním příkladu, v němž budeme přistupovat k prvkům sekvence nulových čísel. Tuto sekvenci lze vytvořit generátorem zeros, který již byl popsán minule:

-- načtení knihovny Lua Fun a současně import symbolů do globálního jmenného prostoru
require "fun"()
 
 
-- generátor nul
print("zeros()")
g = zeros()
 
 
-- pokus o přístup k prvkům sekvence
print("10th item:", nth(10, g))
print("1000th item:", nth(1000, g))
print("5th item:", nth(5, g))
 
 
-- generátor pseudonáhodných čísel
print()
print("rands(100, 200)")
g = rands(100, 200)
 
 
-- pokus o přístup k prvkům sekvence
print("10th item:", nth(10, g))
print("1000th item:", nth(1000, g))
print("5th item:", nth(5, g))
 
 
-- generátor stejných řetězců
print()
print("xrepeat('*')")
g = xrepeat("*")
 
 
-- pokus o přístup k prvkům sekvence
print("10th item:", nth(10, g))
print("1000th item:", nth(1000, g))
print("5th item:", nth(5, g))
 
 
-- generátor druhých mocnin přirozených čísel
print()
print("tabulate()")
g = tabulate(function(x) return (x+1)*(x+1) end)
 
 
-- pokus o přístup k prvkům sekvence
print("10th item:", nth(10, g))
print("1000th item:", nth(1000, g))
print("5th item:", nth(5, g))

Výsledek by měl po spuštění tohoto demonstračního příkladu vypadat následovně:

zeros()
10th item:      0
1000th item:    0
5th item:       0
 
rands(100, 200)
10th item:      155
1000th item:    144
5th item:       151
 
xrepeat('*')
10th item:      *
1000th item:    *
5th item:       *
 
tabulate()
10th item:      100
1000th item:    1000000
5th item:       25

Chování pro neplatné indexy (včetně indexu nulového) můžeme snadno ověřit přímo v interaktivní smyčce REPL programovacího jazyka Lua:

> require "fun"()
> g = zeros()
> print(nth(0, g))
./fun.lua:299: invalid first argument to nth
stack traceback:
        [C]: in function 'assert'
        ./fun.lua:299: in function <./fun.lua:298>
        (...tail calls...)
        stdin:1: in main chunk
        [C]: in ?
 
> print(nth(-1, g))
./fun.lua:299: invalid first argument to nth
stack traceback:
        [C]: in function 'assert'
        ./fun.lua:299: in function <./fun.lua:298>
        (...tail calls...)
        stdin:1: in main chunk
        [C]: in ?

2. Operace prováděné interně ve funkci nth

Zdrojový kód funkce nth byl uveden v úvodní kapitole. Pojďme si nyní otestovat, jak tato funkce pracuje s nekonečnou sekvencí vytvářenou s použitím vlastního generátoru. Původní zdrojový kód generátoru jsme již použili minule – jedná se o generátor druhých mocnin (kvadrátů) kladných čísel:

-- vlastní generátor
function my_generator(x)
    return (x+1)*(x+1)
end

Povšimněte si, že se jedná o čistou funkci, jejíž návratová hodnota závisí pouze na hodnotě vstupního parametru. Navíc tato funkce nemá vedlejší efekty (side effect). Ovšem jeden vedlejší efekt můžeme přidat – funkce může vypisovat svůj vstup. To sice není příliš praktické chování, ale pro ladění se může hodit:

-- vlastní generátor
function my_generator(x)
    print("(iter)", x)
    return (x+1)*(x+1)
end

Tuto funkci použijeme ve vlastním generátoru:

-- inicializovat vlastní generátor
g = tabulate(my_generator)

A následně použijeme společně s funkcí nth pro získání n-tého prvku nekonečné sekvence:

-- načtení knihovny Lua Fun a současně import symbolů do globálního jmenného prostoru
require "fun"()
 
 
-- pomocná funkce pro tisk prvků konečné sekvence
function printSequence(sequence)
    for _, value in iter(sequence) do
        print(value)
    end
end
 
 
-- vlastní generátor
function my_generator(x)
    print("(iter)", x)
    return (x+1)*(x+1)
end
 
 
-- inicializovat vlastní generátor
g = tabulate(my_generator)
 
-- pokus o přístup k prvkům sekvence
 
print()
print("getting 9th item")
g9 = nth(9, g)
 
print()
print("getting 10th item")
g10 = nth(10, g)
 
print()
print("getting 5th item")
g5 = nth(5, g)
 
print()
print("9th item:", g9)
print("10th item:", g10)
print("5th item:", g5)
 
 
-- pokus o přístup k nultému prvku (který neexistuje)
print()
print("getting 0th item")
g0 = nth(0, g)

Podívejme se nyní na chování funkce nth:

getting 9th item
(iter)  0
(iter)  1
(iter)  2
(iter)  3
(iter)  4
(iter)  5
(iter)  6
(iter)  7
(iter)  8
 
getting 10th item
(iter)  0
(iter)  1
(iter)  2
(iter)  3
(iter)  4
(iter)  5
(iter)  6
(iter)  7
(iter)  8
(iter)  9
 
getting 5th item
(iter)  0
(iter)  1
(iter)  2
(iter)  3
(iter)  4
 
9th item:       81
10th item:      100
5th item:       25
 
getting 0th item
lua: ./fun.lua:299: invalid first argument to nth
stack traceback:
        [C]: in function 'assert'
        ./fun.lua:299: in function <./fun.lua:298>
        (...tail calls...)
        19_nth_infinite_sequence_state.lua:56: in main chunk
        [C]: in ?

Vidíme, že v případě nekonečných sekvencí vytvářených generátorem funkce nth musí generátor restartovat od prvního prvku, takže se v tomto případě skutečně jedná o časově relativně náročnou operaci se složitostí O(n). Na to je zapotřebí dávat pozor v reálných programech.

Poznámka: co to znamená v praxi? Neměly by se používat počítané programové smyčky, v nichž se volá nth. Namísto toho by se mělo – pochopitelně pokud je to možné s ohledem na implementovaný algoritmus – využívat klasické rekurze, tedy na funkcích head a tail, popř. pro LISPaře car a cdr.

3. Chování funkce taken pro nekonečné sekvence

Naprosto stejným postupem, tj. upraveným generátorem vypisujícím svůj vstupní parametr:

-- vlastní generátor
function my_generator(x)
    print("(iter)", x)
    return (x+1)*(x+1)
end

můžeme otestovat i chování další funkce nazvané taken. I tuto funkci jsme si popsali minule, takže si dnes pouze připomeňme, že taken umožňuje získat prvních n prvků z nějaké sekvence, ať již se jedná o sekvenci konečnou či nekonečnou. Podívejme se nyní na jednoduchý demonstrační příklad, který nám umožní otestovat základní chování této funkce:

-- načtení knihovny Lua Fun a současně import symbolů do globálního jmenného prostoru
require "fun"()
 
 
-- pomocná funkce pro tisk prvků konečné sekvence
function printSequence(sequence)
    for _, value in iter(sequence) do
        print(value)
    end
end
 
 
-- vlastní generátor
function my_generator(x)
    print("(iter)", x)
    return (x+1)*(x+1)
end
 
 
-- inicializovat vlastní generátor
g = tabulate(my_generator)
 
 
-- první podsekvence
print()
print("take_n(5, sequence)")
print("----------------------")
t5 = take_n(5, g)
print("done\n")
printSequence(t5)
 
 
-- druhá podsekvence
print()
print("take_n(10, sequence)")
print("----------------------")
t10 = take_n(10, g)
print("done\n")
printSequence(t10)
 
 
-- třetí podsekvence
print()
print("take_n(0, sequence)")
print("----------------------")
t0 = take_n(0, g)
print("done\n")
printSequence(t0)

Po spuštění tohoto demonstračního příkladu získáme zajímavé výsledky, které ukazují, že funkce taken interně vytváří novou (pod)sekvenci, ale nijak se nesnaží přistupovat k prvkům původní sekvence (zpráva „done“ je vypsána ještě před ladicími zprávami „(iter)“). To je velmi užitečné, protože obecně platí, že tato funkce má časovou složitost O(1), a to nezávisle na délce sekvence (ta může být i nekonečná). A v mnoha algoritmech lze taken použít, aniž by se posléze všechny prvky výsledné (pod)sekvence musely skutečně vyhodnocovat:

take_n(5, sequence)
----------------------
done
 
(iter)  0
1
(iter)  1
4
(iter)  2
9
(iter)  3
16
(iter)  4
25
 
take_n(10, sequence)
----------------------
done
 
(iter)  0
1
(iter)  1
4
(iter)  2
9
(iter)  3
16
(iter)  4
25
(iter)  5
36
(iter)  6
49
(iter)  7
64
(iter)  8
81
(iter)  9
100
 
take_n(0, sequence)
----------------------
done
 

4. Chování funkce split pro nekonečné sekvence

Další funkcí, která má podobné chování při zpracování nekonečných sekvencí, je funkce split, kterou jsme si taktéž popsali v úvodním článku. Připomeňme si, že tato funkce vrátí dvě podsekvence, přičemž první podsekvence odpovídá volání taken a druhá volání dropn. Chování si ověříme na generátoru, který na standardní výstup vypisuje generované hodnoty:

-- načtení knihovny Lua Fun a současně import symbolů do globálního jmenného prostoru
require "fun"()
 
 
-- pomocná funkce pro tisk několika prvků nekonečné sekvence
function printSequence(sequence)
    for _, value in iter(sequence) do
        print(value)
    end
end
 
 
-- vlastní generátor
function my_generator(x)
    print("(iter)", x)
    return (x+1)*(x+1)
end
 
 
-- inicializovat vlastní generátor
g = tabulate(my_generator)
 
 
-- první dvojice podsekvencí
print()
print("split(5, sequence)")
print("----------------------")
s1, s2 = split(5, g)
print("done")
printSequence(s1)
 
 
-- druhá dvojice podsekvencí
print()
print("split(10, sequence)")
print("----------------------")
s1, s2 = split(10, g)
print("done")
printSequence(s1)
 
 
-- třetí dvojice podsekvencí
print()
print("split(0, sequence)")
print("----------------------")
s1, s2 = split(0, g)
print("done")
printSequence(s1)

Výsledek ukazuje, v jakém okamžiku jsou vyhodnocovány prvky první sekvence i podsekvence. První část původní sekvence je vyhodnocena dvakrát:

split(5, sequence)
----------------------
(iter)  0
(iter)  1
(iter)  2
(iter)  3
(iter)  4
(iter)  0
done
1
(iter)  1
4
(iter)  2
9
(iter)  3
16
(iter)  4
25
 
split(10, sequence)
----------------------
(iter)  0
(iter)  1
(iter)  2
(iter)  3
(iter)  4
(iter)  5
(iter)  6
(iter)  7
(iter)  8
(iter)  9
(iter)  0
done
1
(iter)  1
4
(iter)  2
9
(iter)  3
16
(iter)  4
25
(iter)  5
36
(iter)  6
49
(iter)  7
64
(iter)  8
81
(iter)  9
100
 
split(0, sequence)
----------------------
done

5. Zazipování několika nekonečných sekvencí do nové sekvence

V předchozích kapitolách jsme mohli vidět, že některé funkce musí vyhodnocovat prvky v sekvenci, zatímco jiné tuto časově náročnou operaci neprovádí. Do druhé skupiny patří i funkce nazvaná velmi příznačně zip, o které jsme se zmínili minule. Víme již, že tato funkce „zazipuje“ několik sekvencí do sekvence jediné, přičemž první prvek nové sekvence je složen z prvních prvků sekvencí původních (jedná se o tabulku, resp. přesněji řečeno o n-tici) atd. Pokud této funkci předáme sekvence s konečným počtem prvků, bude výsledkem taková sekvence, jejíž délka bude odpovídat sekvenci nejkratší. Co se však stane v případě, kdy budou všechny vstupní sekvence nekonečné? V tomto případě bude výsledkem taktéž nekonečná sekvence, jejíž prvky nebudou funkcí zip vyhodnocovány. Ostatně se o tom můžeme snadno přesvědčit spuštěním následujícího demonstračního příkladu:

-- načtení knihovny Lua Fun a současně import symbolů do globálního jmenného prostoru
require "fun"()
 
 
-- pomocná funkce pro tisk několika prvků nekonečné sekvence
function printZippedSequence(sequence, n)
    i = 0
    for index, a, b, c in iter(sequence) do
        i = i + 1
        if i > n then
            break
        end
        print(index, a, b, c)
    end
end
 
 
-- vlastní generátor
function my_generator(x)
    return (x+1)*(x+1)
end
 
 
-- inicializovat vlastní generátor
g1 = tabulate(my_generator)
 
-- další generátor nekonečné sekvence 
g2 = xrepeat("*")
 
-- třetí generátor nekonečné sekvence
g3 = zeros()
 
-- vytvoření zipu ze tří sekvencí
z1 = zip(g1, g2, g3)
print()
print("Zipped sequence (first ten items from infinite sequence)")
print("--------------------------------------------------------")
printZippedSequence(z1, 10)

Výsledná sekvence nazvaná z1 bude nekonečná, takže z ní vypíšeme jen prvních deset prvků:

Zipped sequence (first ten items from infinite sequnce)
-------------------------------------------------------
table: 0x55a58afd17d0   1       *       0
table: 0x55a58afd1900   4       *       0
table: 0x55a58afd1a30   9       *       0
table: 0x55a58afc1d20   16      *       0
table: 0x55a58afc1e50   25      *       0
table: 0x55a58afc1f80   36      *       0
table: 0x55a58afc20b0   49      *       0
table: 0x55a58afe5be0   64      *       0
table: 0x55a58afe5d10   81      *       0
table: 0x55a58afe5e40   100     *       0

6. Kdy se tedy vyhodnocují prvky nekonečné sekvence získané funkcí zip?

Nepatrnou úpravou generátoru – konkrétně přidáním ladicí funkce print – můžeme zjistit, ve kterém okamžiku se vlastně vyhodnocují prvky nekonečné sekvence, která byla získána funkcí zip. Upravený zdrojový kód demonstračního příkladu může vypadat následovně:

-- načtení knihovny Lua Fun a současně import symbolů do globálního jmenného prostoru
require "fun"()
 
 
-- pomocná funkce pro tisk několika prvků nekonečné sekvence
function printZippedSequence(sequence, n)
    i = 0
    for index, a, b, c in iter(sequence) do
        i = i + 1
        if i > n then
            break
        end
        print(index, a, b, c)
    end
end
 
 
-- vlastní generátor
function my_generator(x)
    print("(iter)", x)
    return (x+1)*(x+1)
end
 
 
-- inicializovat vlastní generátor
g1 = tabulate(my_generator)
 
-- další generátor nekonečné sekvence 
g2 = xrepeat("*")
 
-- třetí generátor nekonečné sekvence
g3 = zeros()
 
-- vytvoření zipu ze tří sekvencí
z1 = zip(g1, g2, g3)
print()
print("Zipped sequence (first ten items from infinite sequnce)")
print("-------------------------------------------------------")
printZippedSequence(z1, 10)

Po spuštění tohoto demonstračního příkladu je patrné, že samotné zavolání funkce zip nevede k vyhodnocení prvků sekvencí na vstupu. Teprve pokus o přístup k jednotlivým prvkům v iterační smyčce ve svém důsledku vede k volání generátoru:

Zipped sequence (first ten items from infinite sequnce)
-------------------------------------------------------
(iter)  0
table: 0x556e569c8780   1       *       0
(iter)  1
table: 0x556e569c8950   4       *       0
(iter)  2
table: 0x556e569b8d20   9       *       0
(iter)  3
table: 0x556e569b8e80   16      *       0
(iter)  4
table: 0x556e569b8fe0   25      *       0
(iter)  5
table: 0x556e569b9110   36      *       0
(iter)  6
table: 0x556e569b9270   49      *       0
(iter)  7
table: 0x556e569b93d0   64      *       0
(iter)  8
table: 0x556e569dcbb0   81      *       0
(iter)  9
table: 0x556e569dcd10   100     *       0
(iter)  10

7. Zjednodušení programů s využitím funkce each

V předchozích demonstračních příkladech jsme používali klasickou smyčku for pro procházení všemi prvky sekvence. V případě „zazipované“ sekvence složené ze tří konečných sekvencí mohl kód vypadat následovně:

-- pomocná funkce pro tisk prvků konečné sekvence
function printZippedSequence(sequence)
    for _, a, b, c in iter(sequence) do
        print(a, b, c)
    end
end

Ovšem pravděpodobně vás napadlo, že se nejedná o příliš elegantní ani univerzální kód, protože (kromě dalších věcí) musíme dopředu vědět, kolik hodnot každý prvek sekvence obsahuje, nehledě na to, že opakovaný zápis programové smyčky začne být únavný. Ovšem i v tomto případě nám může pomoci knihovna Lua Fun, protože obsahuje funkci (vyššího řádu) nazvanou each. Té se předají dvě hodnoty: první hodnotou je nějaká funkce volaná pro každý prvek sekvence a druhou hodnotou je vlastní sekvence. Funkce each bude postupně iterovat přes prvky sekvence a pro každý volat první funkci. Ta by měla mít nějaký vedlejší efekt. Příkladem může být výpis všech prvků konečné sekvence tím, že pro každý její prvek bude zavolána funkce print:

each(print, sequence)

Použití této velmi užitečné funkce je patrné ze zdrojového kódu dalšího demonstračního příkladu:

-- načtení knihovny Lua Fun a současně import symbolů do globálního jmenného prostoru
require "fun"()
 
 
-- pomocná funkce pro tisk prvků konečné sekvence
function printSequence(sequence)
    each(print, sequence)
end
 
 
-- vlastní generátor
function my_generator(x)
    return (x+1)*(x+1)
end
 
 
-- inicializovat vlastní generátor
g1 = tabulate(my_generator)
 
-- další generátor nekonečné sekvence 
g2 = xrepeat("*")
 
-- třetí generátor nekonečné sekvence
g3 = zeros()
 
-- vytvoření zipu ze tří sekvencí
z1 = zip(g1, g2, g3)
 
-- získání prvních deseti prvků z původně nekonečné sekvence
t = take_n(10, z1)
 
-- výstup na terminál
print()
print("Zipped sequence (first ten items from infinite sequnce)")
print("-------------------------------------------------------")
printSequence(t)

S výsledky, které odpovídají příkazům předchozím:

Zipped sequence (first ten items from infinite sequnce)
-------------------------------------------------------
1       *       0
4       *       0
9       *       0
16      *       0
25      *       0
36      *       0
49      *       0
64      *       0
81      *       0
100     *       0
Poznámka: povšimněte si, jak jsme omezili počet vypisovaných prvků s využitím taken, což je opět lepší, než neustálá kontrola počitadla v programové smyčce.

8. Výběr prvků do nové sekvence funkcí vyššího řádu filter

Funkci zip zmíněnou v předchozích kapitolách pravděpodobně mnoho čtenářů již zná z dalších programovacích jazyků. Týká se to i funkce nazvané filter, která dnes již patří k základní nabídce funkcí i v mainstreamových programovacích jazycích (kde bývá zobecněna pro zpracování libovolných streamů či sekvencí). I tato funkce je funkcí vyššího řádu, a to z toho důvodu, že jako svůj vstup akceptuje jinou funkci. Zde se konkrétně musí jednat o predikát, tedy o funkci vracející pro každý svůj vstup pravdivostní hodnotu true nebo false, popř. nil. Na základě výsledné hodnoty predikátu se právě zpracovávaný prvek buď objeví ve výstupní sekvenci či nikoli – tato funkce tedy obecně vrací sekvenci, ale mnohdy s odlišným počtem prvků, než kolik jich má seznam vstupní (pokud není predikát splněn pro žádný prvek ze vstupní sekvence, bude výsledkem prázdná sekvence, naopak však může vzniknout i sekvence nekonečná).

Podívejme se nyní na použití této funkce při tvorbě sekvence obsahující sudá čísla a jiné sekvence, tentokrát s čísly lichými:

-- načtení knihovny Lua Fun a současně import symbolů do globálního jmenného prostoru
require "fun"()
 
 
-- pomocná funkce pro tisk prvků konečné sekvence
function printSequence(sequence)
    each(print, sequence)
end
 
 
-- vlastní generátor
function my_generator(x)
    return (x+1)*(x+1)
end
 
 
-- inicializovat vlastní generátor
g = tabulate(my_generator)
 
 
-- první podsekvence
print()
print("take_n(10, sequence)")
print("----------------------")
t10 = take_n(10, g)
printSequence(t10)
 
 
-- provést filtraci
f1 = filter(function(x) return x % 2 == 0 end, g)
print()
print("even results")
print("----------------------")
t5 = take_n(5, f1)
printSequence(t5)
 
 
-- provést filtraci
f2 = filter(function(x) return not(x % 2 == 0) end, g)
print()
print("odd results")
print("----------------------")
t5 = take_n(5, f2)
printSequence(t5)

Po spuštění demonstrační příklad nejprve vypíše původní sekvenci, posléze výběr z této sekvence (jen sudá čísla) a následně další výběr (jen liché hodnoty):

take_n(10, sequence)
----------------------
1
4
9
16
25
36
49
64
81
100
 
even results
----------------------
4
16
36
64
100
 
odd results
----------------------
1
9
25
49
81

9. Výběr prvků na základě aplikace predikátu match – výběr řetězců podle vzorku

Kromě výše zmíněné funkce vyššího řádu filter nabízí knihovna Lua Fun programátorům i funkci pojmenovanou grep. A skutečně – tato funkce připomíná chování příkazu grep, protože postupně prochází prvky sekvence (ty by měly obsahovat řetězec) a na základě porovnání hodnoty těchto řetězců se vzorkem se příslušný prvek vloží do nové sekvence či nikoli. Operace prováděné funkcí grep si můžeme ukázat na jednoduchém demonstračním příkladu, který bude zpracovávat sekvenci se jmény všech měsíců. Nejdříve se vypíšou všechny měsíce obsahující písmeno „n“, dále měsíce začínající řetězcem „Cer“, poté měsíce končící řetězcem „en“ a nakonec všechny měsíce, přesněji měsíce odpovídající vzorku „.“:

-- načtení knihovny Lua Fun a současně import symbolů do globálního jmenného prostoru
require "fun"()
 
 
-- sekvence
months = {
    "Leden",
    "Unor",
    "Brezen",
    "Duben",
    "Kveten",
    "Cerven",
    "Cervenec",
    "Srpen",
    "Zari",
    "Rijen",
    "Listopad",
    "Prosinec"
}
 
-- pomocná funkce pro tisk prvků konečné sekvence
function printSequence(sequence)
    each(print, sequence)
end
 
 
print("Mesice obsahujici znak 'n'")
print("--------------------------")
g1 = grep("n", months)
printSequence(g1)
 
 
print()
print("Mesice zacinajici na 'Cer'")
print("--------------------------")
g2 = grep("^Cer", months)
printSequence(g2)
 
 
print()
print("Mesice koncici na 'en'")
print("--------------------------")
g3 = grep("en$", months)
printSequence(g3)
 
 
print()
print("Vsechny mesice")
print("--------------------------")
g4 = grep(".", months)
printSequence(g4)

Výsledky získané po spuštění tohoto příkladu vypadají následovně:

Mesice obsahujici znak 'n'
--------------------------
Leden
Unor
Brezen
Duben
Kveten
Cerven
Cervenec
Srpen
Rijen
Prosinec
 
Mesice zacinajici na 'Cer'
--------------------------
Cerven
Cervenec
 
Mesice koncici na 'en'
--------------------------
Leden
Brezen
Duben
Kveten
Cerven
Srpen
Rijen
 
Vsechny mesice
--------------------------
Leden
Unor
Brezen
Duben
Kveten
Cerven
Cervenec
Srpen
Zari
Rijen
Listopad
Prosinec

10. Rozdělení prvků v sekvenci do dvou sekvencí funkcí partition

Funkce filter popsaná v předchozím textu vybírá ze vstupní sekvence určité prvky na základě vyhodnocení jejich hodnoty (vůči libovolné podmínce). Ovšem někdy se dostaneme do situace, kdy je nutné prvky rozdělit do dvou skupin – prvky odpovídající podmínce a prvky, které podmínce neodpovídají. Toho lze samozřejmě dosáhnout i funkcí filter za předpokladu, že ji zavoláme poprvé s nějakou podmínkou a podruhé s negací podmínky, ovšem v knihovně Lua Fun je dostupná i funkce, která obě operace provede současně. Tato funkce se jmenuje partition a používá se následovně (připomíná funkci split):

-- načtení knihovny Lua Fun a současně import symbolů do globálního jmenného prostoru
require "fun"()
 
 
-- pomocná funkce pro tisk prvků konečné sekvence
function printSequence(sequence)
    each(print, sequence)
end
 
 
-- vlastní generátor
function my_generator(x)
    return (x+1)*(x+1)
end
 
 
-- inicializovat vlastní generátor
g = tabulate(my_generator)
 
 
-- první podsekvence
print()
print("take_n(10, sequence)")
print("----------------------")
t10 = take_n(10, g)
printSequence(t10)
 
 
-- provést filtraci
f1, f2 = partition(function(x) return x % 2 == 0 end, g)
print()
print("even results")
print("----------------------")
t5 = take_n(5, f1)
printSequence(t5)
 
 
-- (filtrace uz byla provedena)
print()
print("odd results")
print("----------------------")
t5 = take_n(5, f2)
printSequence(t5)

V příkladu je vstupní sekvence celých čísel rozdělena na čísla sudá a čísla lichá, takže by výsledek po spuštění příkladu měl vypadat takto:

take_n(10, sequence)
----------------------
1
4
9
16
25
36
49
64
81
100
 
even results
----------------------
4
16
36
64
100
 
odd results
----------------------
1
9
25
49
81

11. Funkce vyššího řádu map

Zatímco v běžných imperativních programovacích jazycích se seznamy zpracovávají prvek po prvku s využitím nějaké formy programové smyčky, v jazyku Lua se při použití knihovny Fun setkáme spíše s aplikací několika funkcí vyššího řádu, které jako svůj vstup akceptují sekvenci a nějakou funkci, která je postupně aplikována buď na prvky sekvence, nebo na prvek sekvence a akumulátor, jehož hodnota se postupně při zpracovávání jednotlivých prvků sekvence mění. Výsledkem bývá buď nová sekvence, nebo výsledná hodnota akumulátoru. Tyto funkce se většinou nazývají map, filter a reduce či foldl. Funkci filter již známe, takže se nyní podívejme na funkci map. Ta taktéž prochází prvky sekvence a aplikuje na ně nějakou funkci, podobně jako each, ovšem namísto využití vedlejšího efektu se na základě návratových hodnot funkce map vytváří nová sekvence prvků.

Následuje příklad použití založený na rozhodování, zda je prvek sudý či nikoli. Druhá část demonstračního příkladu vypočte hodnoty faktoriálu pro prvních deset přirozených čísel:

-- načtení knihovny Lua Fun a současně import symbolů do globálního jmenného prostoru
require "fun"()
 
 
-- pomocná funkce pro tisk prvků konečné sekvence
function printSequence(sequence)
    each(print, sequence)
end
 
 
-- vlastní generátor
function my_generator(x)
    return (x+1)*(x+1)
end
 
 
-- inicializovat vlastní generátor
g = tabulate(my_generator)
 
 
-- první podsekvence
print()
print("take_n(10, sequence)")
print("----------------------")
t10 = take_n(10, g)
printSequence(t10)
 
 
-- provést transformaci
m1 = map(function(x) return x % 2 == 0 end, g)
print()
print("even results?")
print("----------------------")
t5 = take_n(5, m1)
printSequence(t5)
 
 
-- funkce pro výpočet faktoriálu
function factorial(n)
    for i = 1, n-1 do
        n = n * i
    end
    return n
end
 
 
-- provést výpočet faktoriálu
m2 = map(factorial, range(10))
print()
print("factorials")
print("----------------------")
printSequence(m2)

Výsledky po spuštění:

take_n(10, sequence)
----------------------
1
4
9
16
25
36
49
64
81
100
 
even results?
----------------------
false
true
false
true
false
 
factorials
----------------------
1
2
6
24
120
720
5040
40320
362880
3628800

12. Opakování sekvence funkcí/generátorem cycle

V některých situacích se setkáme s požadavkem na to, aby se prvky v sekvenci stále opakovaly dokola. Pochopitelně opět nemusíme pracně psát vlastní generátor nebo sofistikovanou programovou smyčku. Namísto toho můžeme použít funkci nazvanou cycle, která danou sekvenci (konečnou!) bude neustále dokola opakovat a vytvoří z ní tak sekvenci nekonečnou. V dalším demonstračním příkladu je ukázáno, jak se vytvoří sekvence sudých čísel v intervalu 2..10 a z ní následně sekvence, která se neustále opakuje:

-- načtení knihovny Lua Fun a současně import symbolů do globálního jmenného prostoru
require "fun"()
 
 
-- pomocná funkce pro tisk několika prvků nekonečné sekvence
function printSequence(sequence, n)
    i = 0
    for index, value in iter(sequence) do
        i = i + 1
        if i > n then
            break
        end
        print(index, value)
    end
end
 
 
-- inicializovat generátor celých čísel
g = range(2, 10, 2)
 
 
print("original sequence")
print("---------------------------")
printSequence(g, 20)
 
c = cycle(g)
print()
print("cycles of original sequence")
print("---------------------------")
printSequence(c, 20)

Výsledek tohoto příkladu nebude nijak překvapivý:

original sequence
---------------------------
2       2
4       4
6       6
8       8
10      10
 
cycles of original sequence
---------------------------
2       2
4       4
6       6
8       8
10      10
2       2
4       4
6       6
8       8
10      10
2       2
4       4
6       6
8       8
10      10
2       2
4       4
6       6
8       8
10      10

Další příklad používá funkce cycle a zip aby vznikla nová sekvence ukazující, která hodnota je sudá a která lichá:

-- načtení knihovny Lua Fun a současně import symbolů do globálního jmenného prostoru
require "fun"()
 
 
-- pomocná funkce pro tisk několika prvků zazipované konečné sekvence
function printZippedSequence(sequence)
    for index, a, b in iter(sequence) do
        print(index, a, b)
    end
end
 
 
-- inicializovat generátor celých čísel
g = range(1, 20, 1)
 
-- opakující se sekvence dvou slov
c = cycle({"liché", "sudé"})
 
-- vytvoření zipu z obou sekvencí
z = zip(g, c)
 
-- s tiskem výsledku
printZippedSequence(z)

Výsledky:

table: 0x5648aa99dd20   1       liché
table: 0x5648aa99de30   2       sudé
table: 0x5648aa99df40   3       liché
table: 0x5648aa99e050   4       sudé
table: 0x5648aa99e160   5       liché
table: 0x5648aa99e270   6       sudé
table: 0x5648aa97ccb0   7       liché
table: 0x5648aa97cdc0   8       sudé
table: 0x5648aa97ced0   9       liché
table: 0x5648aa97cfe0   10      sudé
table: 0x5648aa97d0f0   11      liché
table: 0x5648aa97d200   12      sudé
table: 0x5648aa97d310   13      liché
table: 0x5648aa97d420   14      sudé
table: 0x5648aa9a0a30   15      liché
table: 0x5648aa9a0b40   16      sudé
table: 0x5648aa9a0c50   17      liché
table: 0x5648aa9a0d60   18      sudé
table: 0x5648aa9a0e70   19      liché
table: 0x5648aa9a0f80   20      sudé

A konečně příklad třetí přidává informaci o tom, které celé číslo je dělitelné třemi, opět na základě použití funkcí range, cycle a zip (bez použití programové smyčky):

-- načtení knihovny Lua Fun a současně import symbolů do globálního jmenného prostoru
require "fun"()
 
 
-- pomocná funkce pro tisk několika prvků zazipované konečné sekvence
function printZippedSequence(sequence)
    for index, a, b, c in iter(sequence) do
        print(index, a, b, c)
    end
end
 
 
-- inicializovat generátor celých čísel
g = range(1, 20, 1)
 
-- opakující se sekvence dvou slov
c1 = cycle({"liché", "sudé"})
 
-- opakující se sekvence tří slov
c2 = cycle({"", "", "a dělitelné třemi"})
 
-- vytvoření zipu z obou sekvencí
z = zip(g, c1, c2)
 
-- s tiskem výsledku
printZippedSequence(z)

Výsledky:

table: 0x558981730950   1       liché
table: 0x558981720cc0   2       sudé
table: 0x558981720df0   3       liché   a dělitelné třemi
table: 0x558981720f70   4       sudé
table: 0x5589817210a0   5       liché
table: 0x558981745030   6       sudé    a dělitelné třemi
table: 0x558981745160   7       liché
table: 0x558981745290   8       sudé
table: 0x5589817453c0   9       liché   a dělitelné třemi
table: 0x5589817497a0   10      sudé
table: 0x5589817498d0   11      liché
table: 0x558981749a00   12      sudé    a dělitelné třemi
table: 0x558981749b30   13      liché
table: 0x558981749c60   14      sudé
table: 0x558981725940   15      liché   a dělitelné třemi
table: 0x558981725a70   16      sudé
table: 0x558981725ba0   17      liché
table: 0x558981725cd0   18      sudé    a dělitelné třemi
table: 0x558981725e00   19      liché
table: 0x558981725f30   20      sudé

13. Kompozice sekvencí

Již víme, jak z několika sekvencí udělat sekvenci jedinou jejich „zazipováním“. Existuje však ještě jeden způsob, jak sekvence nějakým způsobem zkombinovat. Jedná se o operaci pojmenovanou chain, která vytvoří novou sekvenci spojením všech vstupních sekvencí do výsledné řady. Aby byla tato funkce prakticky použitelná je nutné, aby byly všechny sekvence konečné, popř. aby byla nekonečná jen poslední vstupní sekvence. Opět se podívejme na jednoduchý příklad použití:

-- načtení knihovny Lua Fun a současně import symbolů do globálního jmenného prostoru
require "fun"()
 
 
-- pomocná funkce pro tisk několika prvků konečné sekvence
function printSequence(sequence)
    for index, value in iter(sequence) do
        print(index, value)
    end
end
 
 
-- inicializovat generátor celých čísel
g1 = range(2, 10, 2)
 
-- další sekvence
g2 = range(10, 2, -2)
 
-- třetí sekvence
g3 = xrepeat("***")
 
 
-- spojení všech tří sekvencí
chained = chain(g1, g2, g3)
 
-- získání prvních dvaceti prvků z výsledné nekonečné sekvence
trimmed = take_n(20, chained)
 
print()
print("chained and trimmed sequence")
print("----------------------------")
printSequence(trimmed)
 
-- vše v jediném příkazu
trimmed2 = take_n(20, chain(range(2, 10, 2), range(10, 2, -2), xrepeat(100)))
 
print()
print("chained and trimmed sequence")
print("----------------------------")
printSequence(trimmed)

První dvě vstupní sekvence jsou konečné, jen třetí je nekonečná, takže spojení všech tří sekvencí dopadne podle očekávání:

chained and trimmed sequence
----------------------------
table: 0x559dbad78a60   2
table: 0x559dbad78c40   4
table: 0x559dbad68d60   6
table: 0x559dbad68ee0   8
table: 0x559dbad69060   10
table: 0x559dbad69250   10
table: 0x559dbad693a0   8
table: 0x559dbad91020   6
table: 0x559dbad91170   4
table: 0x559dbad912c0   2
table: 0x559dbad91480   ***
table: 0x559dbad915d0   ***
table: 0x559dbad91720   ***
table: 0x559dbad91870   ***
table: 0x559dbad919c0   ***
table: 0x559dbad91b10   ***
table: 0x559dbad91c60   ***
table: 0x559dbad6e7b0   ***
table: 0x559dbad6e900   ***
table: 0x559dbad6ea50   ***
 
chained and trimmed sequence
----------------------------
table: 0x559dbad6f680   2
table: 0x559dbad93df0   4
table: 0x559dbad93f40   6
table: 0x559dbad94090   8
table: 0x559dbad941e0   10
table: 0x559dbad943a0   10
table: 0x559dbad944f0   8
table: 0x559dbad94640   6
table: 0x559dbad94790   4
table: 0x559dbad948e0   2
table: 0x559dbad94aa0   ***
table: 0x559dbad94bf0   ***
table: 0x559dbad94d40   ***
table: 0x559dbad94e90   ***
table: 0x559dbad94fe0   ***
table: 0x559dbad95130   ***
table: 0x559dbad95280   ***
table: 0x559dbad953d0   ***
table: 0x559dbad95520   ***
table: 0x559dbad95670   ***

14. Přidání indexů k prvkům s využitím funkce enumerate

Funkce pojmenovaná enumerate může být užitečná v těch případech, kdy potřebujeme prvky v nějaké sekvenci očíslovat. Samozřejmě je možné enumerate nahradit nějakou kombinací funkcí range a zip, to je ovšem obtížnější v případě, že je vstupní sekvence nekonečná (funkce range se v knihovně Lua Fun chová jinak, než například v programovacím jazyce Clojure). Do kódu předchozího demonstračního příkladu jsme přidali funkci enumerate a rozšířili tak vstupní sekvenci o nový sloupec s pořadovým číslem prvku (opět se čísluje od jedničky, jak je to ostatně v jazyce Lua zvykem):

-- načtení knihovny Lua Fun a současně import symbolů do globálního jmenného prostoru
require "fun"()
 
 
-- pomocná funkce pro tisk několika prvků konečné sekvence
function printSequence(sequence)
    for _, a, b in iter(sequence) do
        print(a, b)
    end
end
 
 
-- inicializovat generátor celých čísel
g1 = range(2, 10, 2)
 
-- další sekvence
g2 = range(10, 2, -2)
 
-- třetí sekvence
g3 = xrepeat("***")
 
 
-- spojení všech tří sekvencí
chained = chain(g1, g2, g3)
 
-- získání prvních dvaceti prvků z výsledné nekonečné sekvence
trimmed = take_n(20, chained)
enumerated = enumerate(trimmed)
 
print()
print("chained and trimmed sequence")
print("----------------------------")
printSequence(enumerated)
 
-- vše v jediném příkazu
trimmed2 = take_n(20, chain(range(2, 10, 2), range(10, 2, -2), xrepeat(100)))
enumerated2 = enumerate(trimmed2)
 
print()
print("chained and trimmed sequence")
print("----------------------------")
printSequence(enumerated2)

Výsledky:

chained and trimmed sequence
----------------------------
1       2
2       4
3       6
4       8
5       10
6       10
7       8
8       6
9       4
10      2
11      ***
12      ***
13      ***
14      ***
15      ***
16      ***
17      ***
18      ***
19      ***
20      ***
 
chained and trimmed sequence
----------------------------
1       2
2       4
3       6
4       8
5       10
6       10
7       8
8       6
9       4
10      2
11      100
12      100
13      100
14      100
15      100
16      100
17      100
18      100
19      100
20      100

15. Proložení prvků v sekvenci konstantními hodnotami

Předposlední funkcí, s níž se setkáme, je funkce nazvaná intersperse (popravdě jsem toto slovo předtím neznal). Tato funkce slouží k vytvoření nové sekvence ze sekvence vstupní. Ovšem v nové sekvenci budou prvky proloženy nějakou další hodnotou, což v praxi znamená, že u konečných sekvencí vznikne nová sekvence s dvojnásobnou délkou:

-- načtení knihovny Lua Fun a současně import symbolů do globálního jmenného prostoru
require "fun"()
 
 
-- pomocná funkce pro tisk několika prvků konečné sekvence
function printSequence(sequence)
    for _, x in iter(sequence) do
        print(x)
    end
end
 
 
-- inicializovat generátor celých čísel
g1 = range(1, 10, 1)
 
g2 = intersperse("---------------------", g1)
 
print()
print("interspersed sequence")
print("---------------------")
printSequence(g2)

Výsledek:

interspersed sequence
---------------------
1
---------------------
2
---------------------
3
---------------------
4
---------------------
5
---------------------
6
---------------------
7
---------------------
8
---------------------
9
---------------------
10
---------------------

16. Funkce vyššího řádu foldl neboli reduce

Ve většině programovacích jazyků inspirovaných funkcionálním programováním se kromě funkcí typu map a filter setkáme i s funkcí vyššího řádu, která se nazývá buď reduce nebo fold. Knihovna Lua Fun pochopitelně není výjimkou, takže i v ní nalezneme tento typ funkce, a to dokonce s oběma jmény. Základní funkcí tohoto typu je funkce nazvaná foldl, která postupně zpracovává všechny prvky sekvence zleva doprava a aplikuje na každý prvek a akumulovanou hodnotu nějakou funkci (tedy foldl 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 foldl. Samotné volání této funkce je ovšem nepatrně složitější, než tomu bylo u map a filter. Je tomu tak z toho důvodu, že kromě zpracovávající funkce (se dvěma parametry) a vstupního seznamu musíme funkci foldl předat i počáteční hodnotu akumulátoru.

Typickým příkladem použití je součet aritmetické řady. Zde je akumulátor inicializován na nulovou hodnotu:

-- načtení knihovny Lua Fun a současně import symbolů do globálního jmenného prostoru
require "fun"()
 
 
-- inicializovat generátor celých čísel
g1 = range(1, 10, 1)
 
-- spočítat součet všech prvků v sekvenci
sum = foldl(function(acc, x) return acc + x end, 0, g1)
 
-- a vypsat ho
print("1 + 2 + ... + 10 = ", sum)

S výsledkem:

1 + 2 + ... + 10 =      55

Dalším poměrně typickým příkladem použití je výpočet faktoriálu a to tak, že nejdříve vytvoříme aritmetickou řadu od 1 do n a posléze prvky této řady vynásobíme. Akumulátor musí být v tomto případě inicializován na jedničku (protože násobení nulou pochopitelně nedává žádané výsledky):

-- načtení knihovny Lua Fun a současně import symbolů do globálního jmenného prostoru
require "fun"()
 
 
for n = 1, 20 do
    -- inicializovat generátor celých čísel
    g1 = range(1, n)
    prod = foldl(function(acc, x) return acc * x end, 1, g1)
    print(n.."! = ", prod)
end

Výsledky:

1! =    1
2! =    2
3! =    6
4! =    24
5! =    120
6! =    720
7! =    5040
8! =    40320
9! =    362880
10! =   3628800
11! =   39916800
12! =   479001600
13! =   6227020800
14! =   87178291200
15! =   1307674368000
16! =   20922789888000
17! =   355687428096000
18! =   6402373705728000
19! =   121645100408832000
20! =   2432902008176640000

Protože se s výpočtem sumy nebo produktu (násobení prvků řady) setkáme velmi často, existují v knihovně Lua Fun pomocné funkce sum a prod, které nahrazují volání foldl:

-- načtení knihovny Lua Fun a současně import symbolů do globálního jmenného prostoru
require "fun"()
 
 
for n = 1, 20 do
    -- inicializovat generátor celých čísel
    g1 = range(1, n)
    prod = product(g1)
    print(n.."! = ", prod)
end
 
-- výpočet na jediném řádku
print()
 
for n = 1, 20 do
    -- inicializovat generátor celých čísel
    print(n.."! = ", product(range(1, n)))
end

Výsledky:

1! =    1
2! =    2
3! =    6
4! =    24
5! =    120
6! =    720
7! =    5040
8! =    40320
9! =    362880
10! =   3628800
11! =   39916800
12! =   479001600
13! =   6227020800
14! =   87178291200
15! =   1307674368000
16! =   20922789888000
17! =   355687428096000
18! =   6402373705728000
19! =   121645100408832000
20! =   2432902008176640000
 
1! =    1
2! =    2
3! =    6
4! =    24
5! =    120
6! =    720
7! =    5040
8! =    40320
9! =    362880
10! =   3628800
11! =   39916800
12! =   479001600
13! =   6227020800
14! =   87178291200
15! =   1307674368000
16! =   20922789888000
17! =   355687428096000
18! =   6402373705728000
19! =   121645100408832000
20! =   2432902008176640000
Poznámka: kromě toho existují i funkce nazvané max a min s podobným významem – postupná redukce vstupní sekvence s akumulací výsledku (zde vyhledání prvku s maximální, resp. s minimální hodnotou).

17. Přehled funkcí deklarovaných v knihovně Lua Fun

V této kapitole bude uveden přehled většiny funkcí deklarovaných v knihovně Lua Fun. Funkce jsou rozděleny podle toho, jakou operaci provádí a pro jaké účely se hodí.

Iterátory, resp. funkce nahrazující klasické iterační smyčky:

# Funkce Stručný popis
1 iter vytvoření iterátoru z různých typů jazyka Lua (pole, mapa, řetězec)
2 each spuštění zvolené funkce pro každý prvek iterátoru
3 for_each jmenný alias pro předchozí funkci
4 foreach jmenný alias pro předchozí funkci

Generátory:

# Funkce Stručný popis
1 range série číselných hodnot mezi zadanými mezemi s určitým krokem mezi hodnotami
2 duplicate sekvence jedné opakující se hodnoty
3 xrepeat alias pro předchozí generátor
4 replicate alias pro předchozí generátor
5 tabulate sekvence generovaná nějakou funkcí nebo uzávěrem
6 zeros série nulových hodnot
7 ones série jedniček
8 rands série pseudonáhodných hodnot

Přístup k prvkům, řezy sekvencí (slicing) atd.:

# Funkce Stručný popis
1 nth získání n-tého prvku sekvence
     
2 head získání prvního prvku v sekvenci
3 car jmenný alias pro předchozí funkci
4 tail sekvence bez prvního prvku
5 cdr jmenný alias pro předchozí funkci
     
6 taken získání prvních n prvků ze sekvence
7 take_while získání prvků ze začátku sekvence na základě zadané podmínky
8 take jmenný alias pro předchozí dvě funkce (v závislosti na typu prvního parametru)
9 dropn přeskočení prvních n prvků ze sekvence
10 drop_while získání nové sekvence získané přeskočením prvků na začátku základě podmínky
11 drop jmenný alias pro předchozí dvě funkce (v závislosti na typu prvního parametru)
     
12 span rozdělení sekvence na dvě části – jako kombinace taken+dropn
13 split jmenný alias pro předchozí funkci
14 split_at jmenný alias pro předchozí funkci

Filtrace (výběr) prvků na základě podmínky:

# Funkce Stručný popis
1 filter výběr prvků ze sekvence na základě kritérií testovaných predikátem
2 remove_if jmenný alias pro předchozí funkci
3 grep výběr prvků ze sekvence na základě kritérií testovaných vzorkem řetězce
4 partition kombinace funkce filter + filter s opačnou podmínkou

Iterace nad všemi prvky sekvence:

# Funkce Stručný popis
1 map aplikace nějaké funkce na všechny prvky sekvence
2 enumerate vytvoření nové sekvence, v níž budou prvky očíslovány
3 intersperse proložení prvků v sekvenci zvolenou konstantní hodnotou

Kompozice sekvencí:

# Funkce Stručný popis
1 zip „zazipování“ dvou nebo většího množství sekvencí do sekvence výsledné
2 cycle generátor sekvence, která neustále opakuje sekvenci vstupní
3 chain spojení dvou nebo většího množství sekvencí do sekvence výsledné

Operátory používané ve funkcích vyššího řádu max, min apod.:

# Funkce Stručný popis
1 operator.le porovnání na relaci menší nebo rovno
2 operator.lt porovnání na relaci menší než
3 operator.eq porovnání na relaci rovnosti
4 operator.ne porovnání na relaci nerovnosti
5 operator.ge porovnání na relaci větší nebo rovno
6 operator.gt porovnání na relaci větší než
     
7 operator.add součet
8 operator.sub rozdíl
9 operator.mul součin
10 operator.div podíl (podle hodnot)
11 operator.truediv dělení reálných čísel
12 operator.floordiv dělení se zaokrouhlením
13 operator.intdiv celočíselné dělení
14 operator.mod zbytek po dělení (modulo)
15 operator.neq změna znaménka
16 operator.pow umocnění
     
17 operator.land operace and
18 operator.lor operace or
19 operator.lnot operace not
20 operator.truth operace not not (převod na true/false)
21 operator.concat spojení řetězců
22 operator.len délka tabulky/řetězce
23 operator.length alias

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. 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 18_nth_infinite_sequence.lua funkce nth aplikovaná na nekonečnou sekvenci https://github.com/tisnik/functional-lua/tree/master/lua_fun/18_nth_in­finite_sequence.lua
2 19_nth_infinite_sequence_state.lua zjištění interního chování funkce nth https://github.com/tisnik/functional-lua/tree/master/lua_fun/19_nth_in­finite_sequence_state.lua
3 20_taken_infinite_sequence.lua zjištění interního chování funkce taken pro nekonečné sekvence https://github.com/tisnik/functional-lua/tree/master/lua_fun/20_ta­ken_infinite_sequence.lua
4 21_splitn_infinite_sequence.lua zjištění interního chování funkce splitn pro nekonečné sekvence https://github.com/tisnik/functional-lua/tree/master/lua_fun/21_split­n_infinite_sequence.lua
5 22_zip_infinite_sequences.lua zazipování několika nekonečných sekvencí do nové sekvence https://github.com/tisnik/functional-lua/tree/master/lua_fun/22_zip_in­finite_sequences.lua
6 23_zip_infinite_sequences_debug.lua zjištění, kdy se vyhodnocují prvky nekonečné sekvence získané funkcí zip https://github.com/tisnik/functional-lua/tree/master/lua_fun/23_zip_in­finite_sequences_debug.lua
7 24_each.lua zjednodušení programů s využitím funkce each https://github.com/tisnik/functional-lua/tree/master/lua_fun/24_each.lua
8 25_filter.lua výběr prvků do nové sekvence funkcí vyššího řádu filter https://github.com/tisnik/functional-lua/tree/master/lua_fun/25_filter.lua
9 26_grep.lua výběr prvků na základě aplikace predikátu match https://github.com/tisnik/functional-lua/tree/master/lua_fun/26_grep.lua
10 27_partition.lua rozdělení prvků v sekvenci do dvou sekvencí funkcí partition https://github.com/tisnik/functional-lua/tree/master/lua_fun/27_par­tition.lua
11 28_map.lua použití funkce vyššího řádu map https://github.com/tisnik/functional-lua/tree/master/lua_fun/28_map.lua
12 29_cycle.lua využití funkce cycle pro tvorbu opakujících se sekvencí https://github.com/tisnik/functional-lua/tree/master/lua_fun/29_cycle.lua
13 30_cycle.lua využití funkce cycle pro tvorbu opakujících se sekvencí https://github.com/tisnik/functional-lua/tree/master/lua_fun/30_cycle.lua
14 31_cycle.lua využití funkce cycle pro tvorbu opakujících se sekvencí https://github.com/tisnik/functional-lua/tree/master/lua_fun/31_cycle.lua
15 32_chain.lua využití funkce chain pro spojení sekvencí do sekvence jediné https://github.com/tisnik/functional-lua/tree/master/lua_fun/32_chain.lua
16 33_enumerate.lua funkce enumerate pro očíslování prvků sekvence https://github.com/tisnik/functional-lua/tree/master/lua_fun/33_e­numerate.lua
17 34_interperse.lua funkce intersperse pro proložení prvků sekvence jinými prvky https://github.com/tisnik/functional-lua/tree/master/lua_fun/34_in­terperse.lua
18 35_foldl.lua klasická funkce vyššího řádu reduce neboli foldl https://github.com/tisnik/functional-lua/tree/master/lua_fun/35_foldl.lua
19 36_factorial.lua výpočet faktoriálu pomocí reduce, explicitní zápis iterace https://github.com/tisnik/functional-lua/tree/master/lua_fun/36_fac­torial.lua
20 37_factorial.lua výpočet faktoriálu pomocí reduce, implicitní zápis iterace https://github.com/tisnik/functional-lua/tree/master/lua_fun/37_fac­torial.lua

19. Články o programovacím jazyce Lua i o technologii LuaJITu

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

  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

Autor článku

Pavel Tišnovský vystudoval VUT FIT a v současné době pracuje ve společnosti Red Hat, kde vyvíjí nástroje pro OpenShift.io.