Obsah
1. Moonscript: jazyk inspirovaný CoffeeScriptem určený pro ekosystém jazyka Lua (2)
2. Funkce, jejíž návratová hodnota se použije jako parametr jiné funkce
3. Metody deklarované s využitím operátoru „tlusté šipky“
4. Zjednodušený přístup k atributům objektů, implicitní parametry metod
5. Problematika zápisu funkcí s mnoha parametry
6. Kdy jsou kulaté závorky při volání funkce povinné?
7. Generátorová notace seznamu
8. Základní syntaxe generátorové notace seznamu
9. Zkrácený zápis iteračního příkazu v generátorové notaci seznamu
10. Filtrace prvků v zápisu generátorové notace seznamu
11. Generátorová notace seznamu založená na numerické smyčce
12. Malé doplnění: operace s prvky při použití numerické smyčky v generátorové notaci seznamu
13. Generátorová notace slovníku
14. Řezy zdrojového seznamu v generátorové notaci seznamu
15. Filtrace prvků zkombinovaná s použitím řezu
16. Objektově orientované programování v jazyce Lua
17. Příklad vytvoření objektu s konstruktorem a dvojicí metod
18. Obsah následující části článku o Moonscriptu
19. Repositář s demonstračními příklady
1. Moonscript: jazyk inspirovaný CoffeeScriptem určený pro ekosystém jazyka Lua (2)
Na úvodní článek o jazyku Moonscript dnes navážeme. V první části jsme si popsali především relativně jednoduchý „syntaktický cukr“, který byl do Moonscriptu přidán, aby programátorům zjednodušil zápis operací, které provádí prakticky dennodenně. Jedná se například o nové operátory spojující přiřazení s další operací, dále pak o přímou podporu pro víceřádkové řetězce, podporu pro interpolaci řetězců nebo o zkrácený zápis funkcí, který je založen na použití operátoru „tenké šipky“. Dnes si ukážeme další vlastnosti Moonscriptu, které již mnohdy musí být do jazyka Lua transformovány složitějším způsobem. Jedná se například o generátorovou notaci seznamu, generátorovou notaci tabulky, popř. o podporu objektově orientovaného programování (což bude ovšem nosné téma třetího článku o Moonscriptu).
Na začátek si připomeňme, jak lze v Moonscriptu definovat novou funkci. Bude se jednat o funkci se dvěma parametry, která bude vracet součet těchto parametrů. V Moonscriptu lze celý zápis provést na jediném řádku s využitím „tenké šipky“:
-- -- Skript zapsaný v jazyce Moonscript -- sum = (x, y) -> x + y print sum 10, 20
Transpřeklad do jazyka Lua je v tomto případě přímočarý:
-- -- Skript transpilovaný do jazyka Lua -- local sum sum = function(x, y) return x + y end return print(sum(10, 20))
2. Funkce, jejíž návratová hodnota se použije jako parametr jiné funkce
Zcela běžně se setkáme s tím, že se má návratová hodnota jedné funkce použít jako parametr funkce jiné. Typické je například volání nějaké funkce s následným výpisem její návratové hodnoty funkcí print. Příklad v jazyku Lua je triviální:
print(sum(10, 20))
V programovacím jazyku Moonscript může být takové volání realizováno bez použití kulatých závorek, ovšem pouze za předpokladu, že transpřekladač bude přesně vědět, jak „rozdělit“ parametry zapsané na jediném řádku. Podívejme se nejprve na (nefunkční) příklad, kdy funkci print předáváme dva parametry – řetězec „sum:“ a výsledek volání sum 10, 20, 30:
-- -- Skript zapsaný v jazyce Moonscript -- sum = (x, y) -> print "sum", x + y print "sum:", sum 10, 20, 30
V tomto konkrétním příkladu nebylo zřejmé, jaké funkci mají patřit parametry 20 a 30, takže transpřekladač provedl tuto transformaci:
-- -- Skript transpilovaný do jazyka Lua -- local sum sum = function(x, y) return print("sum", x + y) end return print("sum:", sum(10, 20, 30))
Abychom dosáhli konkrétního chování, je nutné použít kulaté závorky při volání funkce sum:
-- -- Skript zapsaný v jazyce Moonscript -- sum = (x, y) -> print "sum", x + y print "sum:", sum(10, 20), 30
Výsledek je již v tomto případě korektní a lze ho spustit:
-- -- Skript transpilovaný do jazyka Lua -- local sum sum = function(x, y) return print("sum", x + y) end return print("sum:", sum(10, 20), 30)
3. Metody deklarované s využitím operátoru „tlusté šipky“
Víme již, jak lze deklarovat běžné funkce s využitím operátoru „šipky“, resp. přesněji řečeno „tenké šipky“ (tenká se jí říká proto, že začíná znakem „-“). Ovšem v programovacím jazyce Lua jsou podporovány i metody – viz též sedmnáctou kapitolu. A při volání metod se explicitně nebo implicitně předává i reference na daný objekt, což je reflektováno názvem tohoto parametru – self (jinde this). A právě aby se tato reference nemusela explicitně zapisovat, lze při deklaraci metody namísto „tenké šipky“ použít „tlustou šipku“.
Metoda f1 bez dalších parametrů, která vrací referenci na svůj objekt, může být deklarována následovně:
-- -- Skript zapsaný v jazyce Moonscript -- f1 = => self foo = {} print f1(foo)
Způsob překladu do jazyka Lua je proveden takto:
-- -- Skript transpilovaný do jazyka Lua -- local f1 f1 = function(self) return self end local foo = { } return print(f1(foo))
Nyní se podívejme na nepatrně praktičtější příklad, konkrétně na metodu, která sníží hodnotu atributu balance (předaného objektu) a zadanou hodnotu (explicitní parametr). V jazyce Moonscript je jedna z variant zápisu této funkce následující:
-- -- Skript zapsaný v jazyce Moonscript -- withdraw = (value) => self.balance -= value
Výsledek transpřekladu do jazyka Lua:
-- -- Skript transpilovaný do jazyka Lua -- local withdraw withdraw = function(self, value) self.balance = self.balance - value end
4. Zjednodušený přístup k atributům objektů, implicitní parametry metod
Vzhledem k tomu, že v metodách se prakticky vždy přistupuje k atributům objektu (jehož reference je předána přes automaticky vytvořený parametr self), poskytuje programovací jazyk Moonscript možnost zjednodušeného přístupu k těmto atributům. Namísto zápisu:
self.balance
popř. sémanticky totožného zápisu:
self["balance"]
je možné použít:
@balance
Ukažme si například, jak lze tímto zkráceným způsobem zapsat metodu, která od hodnoty uložené na účtu (tedy v atributu balance) odečte předanou hodnotu:
-- -- Skript zapsaný v jazyce Moonscript -- withdraw = (value) => @balance -= value
Způsob převodu do programovacího jazyka Lua:
-- -- Skript transpilovaný do jazyka Lua -- local withdraw withdraw = function(self, value) self.balance = self.balance - value end
Metody pochopitelně mohou akceptovat nepovinné parametry s nastavenou výchozí hodnotou. Jedná se vlastně o kombinaci zápisu funkce s nepovinnými parametry se zápisem metody s využitím „tlusté šipky“:
-- -- Skript zapsaný v jazyce Moonscript -- withdraw = (value=0) => @balance -= value
Způsob transpřekladu takto zapsané metody do jazyka Lua:
-- -- Skript transpilovaný do jazyka Lua -- local withdraw withdraw = function(self, value) if value == nil then value = 0 end self.balance = self.balance - value end
5. Problematika zápisu funkcí s mnoha parametry
Poněkud problematický může být zápis funkcí nebo metod v případě, že je použito velké množství parametrů, popř. pokud jsou názvy parametrů dlouhé. V takových případech se nemusí hlavička funkce vlézt na jediný řádek, zejména ve chvíli, kdy se dodržuje například limit osmdesáti znaků na programovém řádku. Následuje poněkud umělý příklad s funkcí, která očekává deset parametrů a vrátí jejich součet:
-- -- Skript zapsaný v jazyce Moonscript -- supersum = (a, b, c, d, e, f, g, h, i, j) -> a + b + c + d + e + f + g + h + i + j print supersum 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
Jak samotná deklarace této funkce tak i její volání se přeloží do jazyka Lua takto:
-- -- Skript transpilovaný do jazyka Lua -- local supersum supersum = function(a, b, c, d, e, f, g, h, i, j) return a + b + c + d + e + f + g + h + i + j end return print(supersum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
Parametry funkce v její deklaraci můžeme rozdělit na větší množství řádků, stejně jako výpočet ve funkci realizovaný výrazem. Musíme však dodržet odsazení!:
-- -- Skript zapsaný v jazyce Moonscript -- supersum = (a, b, c, d, e, f, g, h, i, j) -> a + b + c + d + e + f + g + h + i + j print supersum 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
Překlad do jazyka Lua je stále stejný, tj. totožný s předchozím příkladem:
-- -- Skript transpilovaný do jazyka Lua -- local supersum supersum = function(a, b, c, d, e, f, g, h, i, j) return a + b + c + d + e + f + g + h + i + j end return print(supersum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
Dokonce je možné samotné volání funkce s předáním parametrů zapsat na větší množství programových řádků. Opět je vhodné dodržet vizuální odsazení:
-- -- Skript zapsaný v jazyce Moonscript -- supersum = (a, b, c, d, e, f, g, h, i, j) -> a + b + c + d + e + f + g + h + i + j print supersum 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
Výsledek po transpřekladu do jazyka Lua:
-- -- Skript transpilovaný do jazyka Lua -- local supersum supersum = function(a, b, c, d, e, f, g, h, i, j) return a + b + c + d + e + f + g + h + i + j end return print(supersum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
6. Kdy jsou kulaté závorky při volání funkce povinné?
Při zápisu programů v Moonscriptu se většinou obejdeme bez nutnosti použití kulatých závorek (výjimkou jsou pochopitelně složitější aritmetické výrazy). Někdy je ovšem nutné překladači „pomoci“ v určení, kterým funkcím náleží parametry. Jeden z příkladů jsme mohli vidět ve druhé kapitole, situace se ovšem ještě nepatrně mění ve chvíli, kdy se parametry funkce rozepisují na větší množství programových řádků. Podívejme se na demonstrační příklad, ve kterém je volána funkce print se třemi parametry – přičemž konkrétní hodnoty těchto parametrů jsou získány zavoláním funkce sum s parametry 1, 2, 3 (první volání), 4, 5, 6 (druhé volání) a konečně 7, 8, 9 (třetí volání). Díky rozepsání na tři řádky je nutné kulaté závorky uvést pouze při prvním volání funkce sum:
-- -- Skript zapsaný v jazyce Moonscript -- sum = (a, b, c) -> a + b + c print sum(1, 2, 3), sum 4, 5, 6, sum 7, 8, 9
Způsob překladu do jazyka Lua naznačuje, že Moonscript správně pochopil, které parametry patří druhému a třetímu volání funkce sum:
-- -- Skript transpilovaný do jazyka Lua -- local sum sum = function(a, b, c) return a + b + c end return print(sum(1, 2, 3), sum(4, 5, 6), sum(7, 8, 9))
7. Generátorová notace seznamu
V dalším textu se budeme zabývat zejména popisem velmi praktické konstrukce, díky níž je možné v Moonscriptu používat takzvanou generátorovou notaci seznamu, což je poněkud nepřesně přeložený anglický termín list comprehension. O způsobu překladu tohoto termínu se vedly a pravděpodobně dodnes vedou poměrně vášnivé diskuse; já se zde budu držet překladu použitého například v knize Ponořme se do Python(u) 3 (původně Dive Into Python 3), kterou si můžete stáhnout na stránkách http://knihy.nic.cz/, popř. si zde objednat i papírovou verzi knihy.
S využitím generátorové notace seznamu je možné v programovacích jazycích, které tento zápis podporují, zapsat deklaraci vytvoření nového seznamu s využitím seznamu jiného, a to pomocí aplikace nějaké zvolené funkce na všechny prvky zdrojového seznamu. V mnoha programovacích jazycích je nutné pro generátorovou notaci seznamů používat zvláštní příkaz či deklaraci, tj. vlastně novou syntaktickou kategorii. Z těch známějších jazyků podporujících list comprehension se jedná například o Python, Haskell, Scala či Erlang. Programovací jazyk Moonscript taktéž pro generátorovou notaci používá vlastní syntaktická pravidla, která budou ukázána na několika demonstračních příkladech.
8. Základní syntaxe generátorové notace seznamu
Základní způsob zápisu generátorové notace seznamu se do značné míry podobá Pythonu:
doubled = [item * 2 for i, item in ipairs list]
Úplný skript, který nejprve vytvoří seznam s pěti celočíselnými prvky a následně z tohoto seznamu vygeneruje seznam nový (s prvky s dvojnásobnou hodnotou), může být zapsán takto:
-- -- Skript zapsaný v jazyce Moonscript -- print_list = (list) -> print "i", "item" for i, item in ipairs list print i, item print! list = { 1, 2, 3, 4, 5 } print_list list doubled = [item * 2 for i, item in ipairs list] print_list doubled
Převod do jazyka Lua je již komplikovanější, ale stále ještě poměrně dobře čitelný:
-- -- Skript transpilovaný do jazyka Lua -- local print_list print_list = function(list) print("i", "item") for i, item in ipairs(list) do print(i, item) end return print() end local list = { 1, 2, 3, 4, 5 } print_list(list) local doubled do local _accum_0 = { } local _len_0 = 1 for i, item in ipairs(list) do _accum_0[_len_0] = item * 2 _len_0 = _len_0 + 1 end doubled = _accum_0 end return print_list(doubled)
9. Zkrácený zápis iteračního příkazu v generátorové notaci seznamu
V předchozím příkladu jsme použili naprosto standardní programovou smyčku pro průchod prvky seznamu, resp. pole. Připomeňme si, že jak pole, tak i slovníky jsou v jazyce Lua řešeny tabulkou s klíči a hodnotami, přičemž u polí se používají indexy rostoucí od jedničky do délky pole (a taková tabulka je interně optimalizována). Průchod je řešen následovně – zavoláním generátoru ipairs, který pro dané pole (nebo chcete-li seznam) vrací index a hodnotu:
for i, item in ipairs list print i, item
Prakticky stejnou konstrukci jsme použili v zápisu generátorové notace seznamu:
doubled = [item * 2 for i, item in ipairs list]
Ovšem vzhledem k tomu, že se často opakuje nutnost iterovat nad prvky seznamu (a zahodit přitom index), existuje v Moonscriptu i kratší zápis, který sice může vypadat divně, ale je velmi praktický:
doubled = [item * 2 for item in *list]
Tento zápis použijeme v dalším skriptu:
-- -- Skript zapsaný v jazyce Moonscript -- print_list = (list) -> print "i", "item" for i, item in ipairs list print i, item print! list = { 1, 2, 3, 4, 5 } print_list list doubled = [item * 2 for item in *list] print_list doubled
S výsledkem, který je ovšem odlišný od výsledku, který jsme mohli vidět v předchozí kapitole:
-- -- Skript transpilovaný do jazyka Lua -- local print_list print_list = function(list) print("i", "item") for i, item in ipairs(list) do print(i, item) end return print() end local list = { 1, 2, 3, 4, 5 } print_list(list) local doubled do local _accum_0 = { } local _len_0 = 1 for _index_0 = 1, #list do local item = list[_index_0] _accum_0[_len_0] = item * 2 _len_0 = _len_0 + 1 end doubled = _accum_0 end return print_list(doubled)
10. Filtrace prvků v zápisu generátorové notace seznamu
Moonscript je, jak již víme, založen na CoffeeScriptu a ten je inspirován (kromě jiných jazyků) i Pythonem. V Pythonu můžeme v konstrukci generátorové notace seznamu prvky filtrovat, a to například následujícím způsobem, kterým získáme pouze sudé prvky (tedy prvky dělitelné dvěma):
evens = [item for item in list if item % 2 == 0]
V Moonscriptu je zápis nepatrně odlišný, protože se namísto klíčového slova if používá klíčové slovo when. Sémanticky stejný příklad tedy můžeme do Moonscriptu přepsat takto:
evens = [item for item in *list when item % 2 == 0]
Podívejme se nyní na úplný příklad, v němž ze seznamu s prvky 1 až 10 vyfiltrujeme pouze sudé hodnoty, které budou uloženy do nového seznamu:
-- -- Skript zapsaný v jazyce Moonscript -- print_list = (list) -> print "i", "item" for i, item in ipairs list print i, item print! list = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } print_list list evens = [item for item in *list when item % 2 == 0] print_list evens
Transformace tohoto skriptu do jazyka Lua je stále velmi přímočará – provedl se přepis na smyčku s vloženou konstrukcí if-then:
-- -- Skript transpilovaný do jazyka Lua -- local print_list print_list = function(list) print("i", "item") for i, item in ipairs(list) do print(i, item) end return print() end local list = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } print_list(list) local evens do local _accum_0 = { } local _len_0 = 1 for _index_0 = 1, #list do local item = list[_index_0] if item % 2 == 0 then _accum_0[_len_0] = item _len_0 = _len_0 + 1 end end evens = _accum_0 end return print_list(evens)
Pochopitelně si můžeme obě možnosti zkombinovat a vytvořit seznam obsahující původně sudé prvky, ovšem vynásobené konstantou 3 – viz zvýrazněnou část skriptu:
-- -- Skript zapsaný v jazyce Moonscript -- print_list = (list) -> print "i", "item" for i, item in ipairs list print i, item print! list = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } print_list list x = [item*3 for item in *list when item % 2 == 0] print_list x
Výsledek po transpřekladu do programovacího jazyka Lua bude vypadat takto:
-- -- Skript transpilovaný do jazyka Lua -- local print_list print_list = function(list) print("i", "item") for i, item in ipairs(list) do print(i, item) end return print() end local list = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } print_list(list) local x do local _accum_0 = { } local _len_0 = 1 for _index_0 = 1, #list do local item = list[_index_0] if item % 2 == 0 then _accum_0[_len_0] = item * 3 _len_0 = _len_0 + 1 end end x = _accum_0 end return print_list(x)
11. Generátorová notace seznamu založená na numerické smyčce
Předchozí demonstrační příklady byly ve skutečnosti vytvořeny neefektivně, protože nejdříve jsme ručně vytvořili seznam (tabulku) s prvky tvořícími část aritmetické posloupnosti. Takové prvky lze například v Pythonu vytvořit generátorem range. V Moonscriptu ke stejnému účelu poslouží programová smyčka typu for s čítačem (někdy se takové smyčce říká „numerická smyčka“). Vytvoření seznamu s prvky z rozsahu od 1 do 100, které jsou sudé, bude vypadat takto:
evens = [i for i=1,100 when i % 2 == 0]
A celý spustitelný program vytvořený v Moonscriptu:
-- -- Skript zapsaný v jazyce Moonscript -- print_list = (list) -> print "i", "item" for i, item in ipairs list print i, item print! evens = [i for i=1,100 when i % 2 == 0] print_list evens
Zajímavé bude zjistit, jak se interně takový seznam se sudými čísly vytvoří po převodu výše uvedeného programu do jazyka Lua:
-- -- Skript transpilovaný do jazyka Lua -- local print_list print_list = function(list) print("i", "item") for i, item in ipairs(list) do print(i, item) end return print() end local evens do local _accum_0 = { } local _len_0 = 1 for i = 1, 100 do if i % 2 == 0 then _accum_0[_len_0] = i _len_0 = _len_0 + 1 end end evens = _accum_0 end return print_list(evens)
12. Malé doplnění: operace s prvky při použití numerické smyčky v generátorové notaci seznamu
Jen pro úplnost si ukažme příklad, v němž se z původní sekvence prvků od 1 do 100 vytvoří seznam obsahující násobky hodnoty 3, tedy 3, 6, … 300. Jedno z možných řešení vypadá takto:
-- -- Skript zapsaný v jazyce Moonscript -- print_list = (list) -> print "i", "item" for i, item in ipairs list print i, item print! values = [i*3 for i=1,100] print_list values
A výsledný programový kód po transpilaci do jazyka Lua:
-- -- Skript transpilovaný do jazyka Lua -- local print_list print_list = function(list) print("i", "item") for i, item in ipairs(list) do print(i, item) end return print() end local values do local _accum_0 = { } local _len_0 = 1 for i = 1, 100 do _accum_0[_len_0] = i * 3 _len_0 = _len_0 + 1 end values = _accum_0 end return print_list(values)
13. Generátorová notace slovníku
Speciální notace existuje v jazyku Moonscript i pro vytváření slovníků, tedy (v řeči Luy) tabulek, jejichž klíče obecně netvoří posloupnost celých čísel od 1 do len(t), ale mohou být libovolného typu i hodnoty (řetězce atd.), s výjimkou hodnoty nil. Zápis generátorové notace slovníku vypadá následovně:
slovnik = {key, value for key, value in pairs x}
V dalším demonstračním příkladu se prochází slovníkem x a vytváří se nový slovník, který obsahuje shodné klíče, ovšem namísto původních hodnot (což jsou konkrétně řetězce) se zapisuje délka původních hodnot, tedy namísto „Pepek“ je ve výsledném slovníku uložena hodnota 5 atd.:
-- -- Skript zapsaný v jazyce Moonscript -- print_table = (tbl) -> print "key", "value" for key, value in pairs tbl print key, value print! x = { name: "Pepek" surname: "Vyskoc" } lengths = {k,#v for k,v in pairs x} print_table lengths
Transpřeklad do jazyka Lua je stále ještě poměrně dobře čitelný a relativně stručný (ovšem novou notaci pochopitelně překonat nemůže):
-- -- Skript transpilovaný do jazyka Lua -- local print_table print_table = function(tbl) print("key", "value") for key, value in pairs(tbl) do print(key, value) end return print() end local x = { name = "Pepek", surname = "Vyskoc" } local lengths do local _tbl_0 = { } for k, v in pairs(x) do _tbl_0[k] = #v end lengths = _tbl_0 end return print_table(lengths)
14. Řezy zdrojového seznamu v generátorové notaci seznamu
Programovací jazyk Moonscript obsahuje ještě jeden zajímavý syntaktický prvek – při konstrukci seznamu z jiného seznamu jsou podporovány řezy (slice), které známe například z Pythonu, popř. z jazyka Go. Vytvoření řezu je ukázáno v dalším příkladu, který nejprve z původního desetiprvkového seznamu získá prvky s indexy od 4 do 8 a teprve nad nimi provede požadovanou operaci, zde konkrétně operaci vytvoření seznamu nového:
-- -- Skript zapsaný v jazyce Moonscript -- print_list = (list) -> print "i", "item" for i, item in ipairs list print i, item print! list = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } slice = [item for item in *list[4,8]] print_list slice
Transpřeklad do jazyka Lua je poměrně efektivní, což můžeme vidět při pohledu na zápis vygenerované programové smyčky:
-- -- Skript transpilovaný do jazyka Lua -- local print_list print_list = function(list) print("i", "item") for i, item in ipairs(list) do print(i, item) end return print() end local list = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } local slice do local _accum_0 = { } local _len_0 = 1 local _max_0 = 8 for _index_0 = 4, _max_0 < 0 and #list + _max_0 or _max_0 do local item = list[_index_0] _accum_0[_len_0] = item _len_0 = _len_0 + 1 end slice = _accum_0 end return print_list(slice)
15. Filtrace prvků zkombinovaná s použitím řezu
Vzhledem k tomu, že se řezy používají v programové konstrukci generátorové notace seznamu, není asi velkým překvapením, že je lze snadno zkombinovat s filtrací založené na podmínce when. To je ukázáno na dalším příkladu:
-- -- Skript zapsaný v jazyce Moonscript -- print_list = (list) -> print "i", "item" for i, item in ipairs list print i, item print! list = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } slice = [item for item in *list[4,8] when item % 2 == 0] print_list slice
A pro úplnost dodejme ještě skript vzniklý transpřekladem do jazyka Lua:
-- -- Skript transpilovaný do jazyka Lua -- local print_list print_list = function(list) print("i", "item") for i, item in ipairs(list) do print(i, item) end return print() end local list = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } local slice do local _accum_0 = { } local _len_0 = 1 local _max_0 = 8 for _index_0 = 4, _max_0 < 0 and #list + _max_0 or _max_0 do local item = list[_index_0] if item % 2 == 0 then _accum_0[_len_0] = item _len_0 = _len_0 + 1 end end slice = _accum_0 end return print_list(slice)
16. Objektově orientované programování v jazyce Lua
V navazujícím článku si ukážeme, jak je implementováno OOP v Moonscriptu, ovšem možná na tomto místě stojí za připomenutí, jakým způsobem lze přímo v jazyku Lua používat metatabulky a metametody při psaní skriptů využívajících objektově orientovaný přístup. Jazyk Lua sice nenabízí pro deklaraci tříd a objektů vlastní syntaxi, ale to neznamená, že by objektově orientované programování nebylo možné – objekty lze vytvářet buď na základě uzávěrů (closures) při jejichž použití jsou atributy i metody objektu „zabaleny“ právě v uzávěru (ostatně stejný princip je využitý i v některých funkcionálních jazycích), a/nebo lze využít druhého způsobu založeného na asociativních polích a již zmíněných metatabulkách a metametodách. Tvorba objektů je pak ze sémantického hlediska podobná technice používané v původním JavaScriptu, který byl inspirovaný jazykem Self a takzvaným prototypováním. Právě těmito technikami se budeme zabývat v následujícím textu.
Mezi podporované datové typy programovacího jazyka Lua patří i asociativní pole, někdy také nazývané hashmapa (hešmapa) či hešovací mapa. Jedná se o datovou strukturu, v níž jsou uloženy dvojice klíč–hodnota, přičemž klíčem může být hodnota libovolného datového typu kromě typu nil a hodnota může být zcela libovolná (může se jednat i o další asociativní pole, řetězec, funkci atd.). Právě možnost uložení funkce do asociativního pole je důležitá při konstrukci objektů.
Význačným a do jisté míry i unikátním znakem programovacího jazyka Lua je možnost přiřadit prakticky každé hodnotě takzvanou metatabulku (metatable). Přímo z Lua skriptu lze sice přiřadit metatabulku pouze asociativním polím, nikoli ostatním typům hodnot, ovšem z céčkového programu (který volá interpretr Lua) je možné měnit i metatabulku ostatních hodnot pomocí API funkce lua_setmetatable(). Metatabulka je běžné asociativní pole, se kterým lze provádět stejné operace, jako s každým jiným asociativním polem v Lue, ovšem význam některých položek uložených v tomto poli má speciální význam při práci s danou hodnotou (povšimněte si, že metatabulka je, stejně jako typ, přiřazena přímo hodnotě a nikoli proměnné, což je význačný rys prakticky všech dynamicky typovaných jazyků). Metatabulku je možné asociativnímu poli přiřadit funkcí setmetatable(objekt, metatabulka), opětovné získání metatabulky se provádí funkcí getmetatable(). V případě, že se při volání setmetatable() místo metatabulky předá konstanta nil, je původní metatabulka odstraněna. Naopak funkce getmetatable() může vrátit konstantu nil v případě, že žádná metatabulka nebyla ještě objektu (asociativnímu poli) přiřazena.
17. Příklad vytvoření objektu s konstruktorem a dvojicí metod
Ukažme si nyní, jak lze reprezentovat objekt, jak lze vytvořit jeho konstruktor a metody. Objektem může být běžná tabulka, jejíž atributy budou atributy objektu:
Account = {balance = 0}
Konstruktor je ve skutečnosti tvořen metodou, které se předává reference self. V konstruktoru měníme tzv. událost __index v metatabulce; tím je zajištěn přístup k metodám pro všechny objekty odvozené od Account:
function Account:new(o) -- create object if user does not provide one o = o or {} setmetatable(o, self) self.__index = self return o end
Metody lze definovat dvěma způsoby. Buď s explicitním použitím parametru self (tj. jako v Pythonu):
function Account.withdraw(self, value) self.balance = self.balance - value end
nebo s využitím syntaktického cukru, kdy se namísto tečky zapíše dvojtečka a první parametr self se vynechá:
function Account:deposit(value) self.balance = self.balance + value end
Konstrukce objektů a zavolání jejich metod, opět s použitím dvojtečky:
local a = Account:new{balance=0} local b = Account:new{balance=50} a:withdraw(100.00) b:withdraw(100.00) a:deposit(200.00) b:deposit(200.00)
Celý skript vypadá zbytečně složitě, ale jak uvidíme příště, je možné v Moonscriptu mnohý „boilerplate“ zcela odstranit:
Account = {balance = 0} function Account:new(o) -- create object if user does not provide one o = o or {} setmetatable(o, self) self.__index = self return o end function Account.withdraw(self, value) self.balance = self.balance - value end function Account:deposit(value) self.balance = self.balance + value end local a = Account:new{balance=0} local b = Account:new{balance=50} print("account A", a.balance) print("account B", b.balance) a:withdraw(100.00) b:withdraw(100.00) print("account A", a.balance) print("account B", b.balance) a:deposit(200.00) b:deposit(200.00) print("account A", a.balance) print("account B", b.balance)
18. Obsah následující části článku o Moonscriptu
Ve třetí části článku o programovacím jazyce Moonscript si ukážeme, jaké nové syntaktické konstrukce umožňují deklaraci tříd a konstrukci objektů, tedy instancí těchto tříd. Jedná se o (v kontextu ekosystému programovacího jazyka Lua) zcela nový koncept, který se nepodobá způsobu práce s objekty, který jsme si ve stručnosti přiblížili v rámci předchozí kapitoly a který byl sice postaven na jednoduchých základech, ovšem nepodobal se dnes přece jen populárnějším (resp. přesněji řečeno mainstreamovým) objektovým systémům.
19. Repositář s demonstračními příklady
Všechny minule i dnes popsané demonstrační příklady určené pro Moonscript byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/moonscript-examples. Příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes prozatím stále velmi malý) repositář:
20. Odkazy na Internetu
- Stránky projektu Moonscript
https://moonscript.org/ - Moonscript na GitHubu
https://github.com/leafo/moonscript - MoonScript online compiler
https://moonscript.org/compiler/ - Vydání Moonscriptu
https://github.com/leafo/moonscript/releases - Moonscript-vim
https://github.com/leafo/moonscript-vim - Moonscript Examples
https://github.com/leafo/moonscript/wiki/Moonscript-Examples - CoffeeScript
https://coffeescript.org/ - CoffeeScript na Wikipedii
https://en.wikipedia.org/wiki/CoffeeScript - CoffeeScript: řádně oslazený JavaScript
https://zdrojak.cz/clanky/coffeescript-radne-oslazeny-javascript/ - CoffeeScript: druhá dávka steroidů pro vaše skripty
https://zdrojak.cz/clanky/coffeescript-druha-davka-steroidu-pro-vase-skripty/ - Why CoffeeScript is still alive
https://codeburst.io/why-coffeescript-is-still-alive-aeb369b91b85 - The CoffeeScript Wiki
https://github.com/jashkenas/coffeescript/wiki - CoffeeScript In The Wild
https://github.com/jashkenas/coffeescript/wiki/In-The-Wild - How CoffeeScript Got Forgotten
https://betterprogramming.pub/how-coffeescript-got-forgotten-812328225987 - Objektově orientované programování v jazyku Lua
https://www.root.cz/clanky/objektove-orientovane-programovani-v-lua/ - Objektově orientované programování v jazyku Lua (II)
https://www.root.cz/clanky/objektove-orientovane-programovani-v-lua-ii/ - ULua: Universal Lua Distribution
https://ulua.io/index.html - LuaRocks
https://luarocks.org/ - Awesome Lua – A curated list of quality Lua packages and resources.
https://github.com/LewisJEllis/awesome-lua - LuaJIT
https://luajit.org/ - Running LuaJIT
https://luajit.org/running.html - LuaJIT na GitHubu
https://github.com/luajit - Lua Implementations
http://lua-users.org/wiki/LuaImplementations - Coconut: funkcionální jazyk s pattern matchingem kompatibilní s Pythonem
https://www.root.cz/clanky/coconut-funkcionalni-jazyk-s-pattern-matchingem-kompatibilni-s-pythonem/ - Coconut aneb funkcionální nadstavba nad Pythonem (2.část)
https://www.root.cz/clanky/coconut-aneb-funkcionalni-nadstavba-nad-pythonem-2-cast/ - Coconut: Simple, elegant, Pythonic functional programming
http://coconut-lang.org/ - coconut 1.1.0 (Python package index)
https://pypi.python.org/pypi/coconut/1.1.0 - Coconut Tutorial
http://coconut.readthedocs.io/en/master/HELP.html - Coconut FAQ
http://coconut.readthedocs.io/en/master/FAQ.html - Coconut Documentation
http://coconut.readthedocs.io/en/master/DOCS.html - Coconut na Redditu
https://www.reddit.com/r/Python/comments/4owzu7/coconut_functional_programming_in_python/ - Repositář na GitHubu
https://github.com/evhub/coconut - patterns
https://github.com/Suor/patterns - Source-to-source compiler
https://en.wikipedia.org/wiki/Source-to-source_compiler - The Lua VM, on the Web
https://kripken.github.io/lua.vm.js/lua.vm.js.html - Lua.vm.js REPL
https://kripken.github.io/lua.vm.js/repl.html - lua2js
https://www.npmjs.com/package/lua2js - Wisp na GitHubu
https://github.com/Gozala/wisp - Wisp playground
http://www.jeditoolkit.com/try-wisp/ - REPL v prohlížeči
http://www.jeditoolkit.com/interactivate-wisp/ - Minification (programming)
https://en.wikipedia.org/wiki/Minification_(programming) - Roblox
https://en.wikipedia.org/wiki/Roblox - Category:Lua (programming language)-scriptable game engines
https://en.wikipedia.org/wiki/Category:Lua_(programming_language)-scriptable_game_engines - Goodbye Lua (shrnutí následujícího článku)
https://www.reddit.com/r/lua/comments/4ld6ao/goodbye_lua/ - Goodbye, Lua
https://realmensch.org/2016/05/28/goodbye-lua/ - 6th Edition – ECMAScript 2015
https://en.wikipedia.org/wiki/ECMAScript#6th_Edition_%E2%80%93_ECMAScript_2015
Autor: Pavel Tišnovský 2021