Obsah
1. Programovací jazyk Lua: stálice na poli programovacích jazyků
3. Lua je stabilním jazykem, pro některé účely však až příliš stabilním
6. Program typu „Hello, world“ ve variantě pro jazyky Lua i Shine
7. Volání funkcí s větším počtem parametrů
8. Zápis komentářů do programového kódu
9. Definice funkce, volání funkce
10. Specifikace výchozích hodnot parametrů volané funkce
12. Specifikace typu globálních i lokálních proměnných
13. Viditelnost proměnných: rozdíl mezi jazykem Lua a Shine
14. Globální a lokální proměnná se stejným jménem
15. Nelokální proměnné a uzávěry
18. Repositář s demonstračními příklady
19. Odkazy na relevantní články a seriály na Rootu
1. Programovací jazyk Lua: stálice na poli programovacích jazyků
S programovacím jazykem Lua jsme se již na stránkách tohoto serveru setkali, a to dokonce již mnohokrát (i když se mnohdy jedná o poněkud starší články). Většina základních informací o tomto programovacím jazyku je shrnuta do seriálu Programovací jazyk Lua a taktéž jsme se Luou zabývali ve druhém seriálu nazvaném Torch: framework pro strojové učení. Víme již, že se tento programovací jazyk stal oblíbený například mezi tvůrci her, v nichž je použit pro skriptování (a nutno dodat, že se v této oblasti stále používá). To ovšem není zdaleka vše, protože Lua je použita například i v systému LuaTeX, v databázi Redis, ve webovém serveru Nginx a v neposlední řadě i v textovém editoru Neovim, což je v několika ohledech vylepšená varianta slavného Vimu. Jednoduše použitelná syntaxe a současně i poměrně velká vyjadřovací schopnost jazyka Lua by však pravděpodobně nedostačovala pro jeho masovější rozšíření, a to zejména v situaci, kdy tuto niku programovacích jazyků do značné míry okupuje programovací jazyk Python, kterému se již úspěšně podařilo v menší či větší míře „odstavit“ některé konkurenty.
Hlavním důvodem, proč jsou některé hry, například Escape from Monkey Island, Grim Fandango, Fish Fillets, Neverwinter Nights či MDK2 (což jsou, pravda, starší tituly) z menší či větší části naprogramované právě v jazyku Lua, spočívá v tom, že kombinace nízkoúrovňového a skriptovacího jazyka umožňuje soustředit se při vývoji na podstatné věci – herní engine vytvořit co nejefektivnější s využitím všech možností nízkoúrovňového jazyka a naopak herní scénář a logiku hry naskriptovat s co největším urychlením cyklu oprava–překlad–spuštění.
V mnoha případech se také využívá další užitečné vlastnosti jazyka Lua – celý překladač i interpret vygenerovaného bajtkódu (popř. pouze interpret) je možné velmi snadno zabudovat do jiné aplikace, přičemž se výsledná velikost spustitelného souboru této aplikace zvětší o cca 70 kB (popř. lze volat dynamickou knihovnu o řádově stejné velikosti), což není mnoho, navíc když si uvědomíme, že dostáváme k dispozici plnohodnotný vysokoúrovňový programovací jazyk (ostatně Lua se díky své malé velikosti používá i pro pouhé zpracování konfiguračních souborů, které díky tomu mohou obsahovat různé konstanty, výrazy atd.).
2. LuaJIT
Ekosystém programovacího jazyka Lua sice není tak rozsáhlý, jako je tomu například v případě Pythonu a Javy (nemluvě již o JavaScriptu), ale i přesto v něm nalezneme poměrně zajímavé technologie. Kromě standardního interpretru jazyka Lua tak existují transpilery tohoto jazyka, například lua2js, virtuální stroje pro Luu běžící ve webovém prohlížeči (LuaVM) atd. Ovšem jedním z nejdůležitějších projektů je LuaJIT. LuaJIT je původně z velké části dílem jediného programátora, který se jmenuje Mike Pall. To je velmi zajímavá skutečnost, zvláště když si uvědomíme, že LuaJIT je v současné verzi velmi kvalitní produkt podporující větší množství procesorových architektur (na jiných JITech se běžně podílejí minimálně desítky vývojářů).
LuaJIT je skutečným just-in-time překladačem, který je rozdělen do několika modulů, které při spuštění aplikace vytvořené v programovacím jazyku Lua musí vzájemně spolupracovat. Prvním modulem je překladač sloužící pro kompilaci zdrojového kódu napsaného v Lue do mezijazyka, který budeme v dalším textu zkráceně označovat IR – Intermediate Representation. IR je navržen takovým způsobem, aby mohl být buď interpretován (jde o ty části kódu, které nejsou spouštěny příliš často) nebo překládán do nativního strojového kódu s využitím JITu (většinou se jedná o opětovně spouštěné části kódu).
Důležité je, že v projektu LuaJIT se využívá takzvaný „trasovací JIT překladač“, který se v několika ohledech odlišuje od dnes asi známějších JIT překladačů typu hot spot (typickým příkladem je HotSpot JIT použitý v JVM, jehož poměrně podrobnému popisu jsme se již v tomto seriálu kdysi věnovali).
Činnost trasovacích JITů je založena na dvou snadno pochopitelných předpokladech. Prvním předpokladem je, že typické aplikace tráví nejvíce času (přesněji řečeno strojového času) v programových smyčkách (tento předpoklad je někdy rozšířen i o tail rekurzi). Druhým předpokladem je, že pokud se vykonává programová smyčka, bude cesta v kódu pravděpodobně vždy stejná, popř. v horším případě že bude existovat jen několik cest v programovém kódu (cestou je myšlena sekvence instrukcí). Trasovací JITy založené na těchto předpokladech se soustředí na detekci tzv. hot-loops, tedy často vykonávaných programových smyček. Tyto smyčky jsou následně optimalizovány a přeloženy do nativního kódu mikroprocesoru.
Při optimalizacích se provádí mnoho operací, s nimiž se můžeme setkat i s běžných překladačích – eliminace mrtvého kódu (zmenšují se nároky na instrukční cache), rozbalení smyček (snižuje se počet skoků a tím pádem se i vylepšuje využití instrukční pipeline) atd. Detekce hot-loops byla v tradičních trasovacích JITech implementována analýzou zpětných podmíněných skoků (vedoucích na začátek smyčky), ovšem v LuaJITu to není nutné, a to díky speciálním instrukcím bajtkódu: LOOP a FORI.
3. Lua je stabilním jazykem, pro některé účely však až příliš stabilním
Již v úvodní kapitole jsme se zmínili o tom, že jednou z předností programovacího jazyka Lua je jeho jednoduchá a snadno naučitelná syntaxe a relativně dobré vyjadřovací schopnosti. Samotný programovací jazyk je navíc i velmi stabilní, což zde konkrétně znamená, že se nevydal cestou Pythonu, jenž je neustále rozšiřován o další a další programové konstrukce. To je na jednu stranu (v některých oborech) poměrně velká výhoda, na stranu druhou však nebudeme skrývat fakt, že některé vlastnosti jazyka Lua jsou buď pro programátory poněkud neobvyklé či zcela zmatečné a některé jazykové konstrukce prakticky úplně chybí (zejména v porovnání s mainstreamem).
Mezi neobvyklé a možná i matoucí vlastnosti programovacího jazyka Lua patří zejména způsob práce s tabulkami (což je jediná „univerzální“ datová struktura kombinující vlastnosti běžných polí se slovníky). Prvky pole jsou indexovány od jedničky a možnost mít v jedné tabulce jak prvky indexované celým číslem, tak i jiným klíčem ovlivňuje sémantiku programové smyčky for-each (rozdíl mezi chováním pairs a ipairs). Pro některé vývojáře může být matoucí i fakt, že lokální proměnné je zapotřebí explicitně označit klíčovým slovem local, jinak by se jednalo o proměnné globální či nelokální. To je v poměrně ostrém kontrastu s jazykem Python, v němž jsou naopak nijak neoznačené proměnné považovány za lokální (což s sebou ovšem nese komplikace u nelokálních proměnných, které je nutné označit relativně novým klíčovým slovem nonlocal – o tomto problému jsme se mimochodem zmiňovali v souvislosti s uzávěry).
Mezi chybějící či zdánlivě chybějící vlastnosti jazyka Lua patří podpora pro objektově orientované programování. Ve skutečnosti je možné i v Lue pracovat s objekty a jejich „šablonami“ (což zhruba odpovídá třídám), ovšem jedná se o přístup, který je poněkud specifický a pro programátory přicházející z jiných programovacích jazyků neobvyklý. Dalším chybějícím konceptem, resp. přesněji řečeno technikou, která je potenciálně velmi užitečná, je podpora pro dekorátory (například tak, jak jsou využity právě v „konkurenčním“ programovacím jazyku Python). A taktéž chybí konstrukce podobná with použitelná společně se správci kontextů (context managers). A v neposlední řadě (a pro mnohé na prvním místě) je chybějící podpora pro specifikaci typů parametrů a proměnných.
4. Od jazyku Lua ke Shine
Řešením některých výše zmíněných problémů a nedostatků programovacího jazyka Lua by se mohl zdát relativně nový a neznámý programovací jazyk nazvaný Shine. Což je mimochodem jméno, které dobře zapadá do konceptu jmen souvisejících s Měsícem – mnoho projektů v ekosystému jazyka Lua je pojmenováno podobně; ostatně v chybových hlášeních níže objevíme slovo „lunokhod“.
Tento jazyk do Luy přidává nové jazykové konstrukce, vylepšuje práci s tabulkami a řetězci, zlepšuje pravidla pro viditelnosti proměnných a taktéž přidává podporu pro zápis typů. Skripty jsou přitom nikoli pouze interpretovány, ale JITovány, takže výsledné programy jsou mnohem rychlejší, než při pouhé interpretaci.
5. Překlad jazyka Shine
Interpret s JIT (shine) i překladač do bajtkódu (shinec) je možné si snadno přeložit (což je většinou i nutnost, pokud Shine nenaleznete v repositářích použité distribuce Linuxu). K tomu je zapotřebí překladač céčka (GCC), nástroj make a taktéž autotools.
Nejprve naklonujeme repositář se zdrojovými kódy Shine:
$ git clone git@github.com:richardhundt/shine.git Cloning into 'shine'... remote: Enumerating objects: 1696, done. remote: Counting objects: 100% (3/3), done. remote: Compressing objects: 100% (3/3), done. remote: Total 1696 (delta 0), reused 1 (delta 0), pack-reused 1693 Receiving objects: 100% (1696/1696), 597.94 KiB | 1.28 MiB/s, done. Resolving deltas: 100% (1010/1010), done.
Přejdeme do adresáře, který byl naklonováním vytvořen:
$ cd shine
Spustíme překlad přímo zadáním make:
$ make
Překlad se zahájí operacemi, které nejsou příliš zajímavé:
... ... ... mkdir -p /tmp/ramdisk/shine/build/deps mkdir -p /tmp/ramdisk/shine/build/lang mkdir -p /tmp/ramdisk/shine/build/core mkdir -p /tmp/ramdisk/shine/build/libs ... ... ...
Následuje ovšem zajímavější operace – získání dalších modulů, například modulu s implementací upravené varianty LuaJITu. Zdrojový kód této VM se naklonuje a taktéž přeloží:
git submodule update --init /tmp/ramdisk/shine/deps/tvmjit Submodule 'deps/tvmjit' (https://github.com/richardhundt/tvmjit.git) registered for path 'deps/tvmjit' Cloning into '/tmp/ramdisk/shine/deps/tvmjit'... Submodule path 'deps/tvmjit': checked out 'b58b5bef683f28cfb47df2f9bd0ed092f65b5132' make PREFIX=/usr/local MULTILIB= TRAVIS=1 -C /tmp/ramdisk/shine/deps/tvmjit make[1]: Entering directory '/tmp/ramdisk/shine/deps/tvmjit' ==== Building TvmJIT 0.1.3 ==== make -C src make[2]: Entering directory '/tmp/ramdisk/shine/deps/tvmjit/src' HOSTCC host/minilua.o HOSTLINK host/minilua DYNASM host/buildvm_arch.h HOSTCC host/buildvm.o
Výsledkem překladu je dvojice spustitelných souborů shine a shinec uložená v podadresáři build:
$ ls -l build/shine* -rwxrwxr-x 1 ptisnovs ptisnovs 708520 Mar 29 09:21 build/shine -rwxrwxr-x 1 ptisnovs ptisnovs 704384 Mar 29 09:21 build/shinec -rw-rw-r-- 1 ptisnovs ptisnovs 2915 Mar 29 09:21 build/shinec.o -rw-rw-r-- 1 ptisnovs ptisnovs 3516 Mar 29 09:21 build/shine.o
Jedná se o poměrně velké soubory (700 kB), resp. přesněji řečeno jsou velké z pohledu ekosystému jazyka Lua, zatímco v jiných ekosystémech by se jednalo o zanedbatelnou velikost. Můžeme se pokusit o zmenšení pomocí nástroje strip:
$ strip build/shine $ strip build/shinec
Výsledné velikosti:
$ ls -l build/shine* -rwxrwxr-x 1 ptisnovs ptisnovs 637624 Mar 29 09:23 build/shine -rwxrwxr-x 1 ptisnovs ptisnovs 633528 Mar 29 09:24 build/shinec -rw-rw-r-- 1 ptisnovs ptisnovs 2915 Mar 29 09:21 build/shinec.o -rw-rw-r-- 1 ptisnovs ptisnovs 3516 Mar 29 09:21 build/shine.o
Spustitelný soubor shinec dokáže přeložit zdrojové kódy do bajtkódu nebo do mezikódu a taktéž dokáže zobrazit strukturu kódu po převedení do formy stromu. K popisu tohoto nástroje se dostaneme příště:
$ build/shinec usage: build/shinec [options]... input output. Available options are: -t type Output file format. -b List formatted bytecode. -n name Provide a chunk name. -g Keep debug info. -p Print the parse tree. -o Print the opcode tree.
Spustitelný soubor shine představuje interpret jazyka Shine vybavený JIT překladačem. Je možné ho použít samostatně, tj. zbytek celého repositáře není nutné mít k dispozici pro správnou verzi interpretru:
$ build/shine Shine 0.1.1 -- Copyright (C) 2013-2016 Richard Hundt. shine>
6. Program typu „Hello, world“ ve variantě pro jazyky Lua i Shine
Při prakticky každém popisu nového programovacího jazyka se začíná programem, který po svém spuštění vypíše zprávu „Hello, world!“ nebo její obdobu. Jak v jazyku Lua, tak i v jazyku Shine je zápis takového programu totožný – zavolá se vestavěná funkce print, které se předá řetězec se zprávou:
print("Hello, world!")
Navíc jak jazyk Lua, tak i Shine podporuje vynechání závorek okolo argumentu funkce v případě, že se funkci předává jen jediný argument. Můžeme tedy pouze psát:
print "Hello, world!"
7. Volání funkcí s větším počtem parametrů
V programovacím jazyku Lua je možné kulaté závorky okolo argumentů funkce vynechat jen v případě, že se předává jediný argument. To tedy znamená, že následující příkaz je nekorektní:
print 1, 2, 3
Na tuto chybu nás interpret jazyka Lua upozorní:
$ lua 03_print_values.lua lua: 03_print_values.lua:1: syntax error near '1'
Naproti tomu programovací jazyk Shine povoluje tento zápis povoluje:
print 1, 2, 3
$ ./shine 03_print_values.shn 1 2 3
$ ./shine 03_print_values.lua Error: [string "shine"]:0: [string "lunokhod"]:0: 03_print_values.lua:1: unexpected symbol near 1 stack traceback: [C]: in function 'assert' [string "shine"]: in main chunk [string "shine"]: in main chunk [C]: at 0x564ce89e7510
8. Zápis komentářů do programového kódu
Připomeňme si, že v jazyku Lua jsou podporovány dva typy komentářů. Jednořádkový komentář začíná dvěma pomlčkami, podobně jako v jazyku SQL (což se mimochodem zapisuje rychleji, než například znak #):
-- jednoradkovy komentar
Víceřádkový komentář je zapsán stylem:
--[[ viceradkovy komentar ]]
Toto je tedy zcela korektní skript zpracovatelný jak intepretrem jazyka Lua, tak i Shine:
-- jednoradkovy komentar print("Hello") --[[ viceradkovy komentar ]] print("world")
V jazyce Shine je k dispozici ještě jeden typ víceřádkového komentáře, který je primárně určen pro to, aby se daná (zakomentovaná) část kódu zpracovala dalšími nástroji. Tento zápis je nepatrně komplikovanější, ale umožňuje specifikovat symbol pro ukončení komentáře. Zápis vypadá následovně:
--:{zvolený-symbol}: specialni viceradkovy komentar :{zvolený-symbol}:
Následující skript je tedy zpracovatelný jen interpretrem jazyka Shine:
-- jednoradkovy komentar print("Hello") --[[ viceradkovy komentar ]] print("world") --:END: specialni viceradkovy komentar :END: print("!")
Navíc je možné (například) do symbolu s komentářem zapsat i nějaké parametry, které bude interpret Shine ignorovat, ale které mohou být použité jiným nástrojem, typicky pro vygenerování nápovědy, „stubu“ pro další jazyky atd. Příklad specifikace argumentu „use-asciidoc“:
--:format(use-asciidoc): specialni viceradkovy komentar :format:
9. Definice funkce, volání funkce
Základním stavebním prvkem pro tvorbu abstrakcí v programech psaných v jazycích Lua i Shine jsou funkce. Ty se deklarují v obou jazycích stejným způsobem, i když v dalším textu uvidíme, že Shine nabízí i další možnosti a rozšíření. Podívejme se nejdříve na deklaraci jednoduché funkce se dvěma parametry následovanou zavoláním této funkce:
function print_two_values(a, b) print(a, b) end print_two_values(1, 2)
Jazyk Shine navíc umožňuje (kromě dalších možností) volání funkce bez zápisu kulatých závorek, a to i v případě, že má funkce větší množství parametrů (což jsme již ostatně viděli na příkladu s print):
function print_two_values(a, b) print(a, b) end print_two_values(1, 2) print_two_values 1, 2
10. Specifikace výchozích hodnot parametrů volané funkce
V programovacím jazyku Shine je možné, ostatně podobně jako i v dalších moderních programovacích jazycích, specifikovat výchozí hodnoty parametrů funkce. Pokud se hodnoty těchto parametrů (tedy argumenty) explicitně při volání funkce neuvedou, použije se právě tato výchozí hodnota:
function print_two_values(a, b=2) print(a, b) end print_two_values 100, 200 print_two_values 100
Výsledkem budou tyto zprávy:
100 200 100 2
Pokud se výchozí hodnoty specifikují u všech parametrů, budou při volání funkce všechny argumenty nepovinné:
function print_two_values(a=1, b=2) print(a, b) end print_two_values 100, 200 print_two_values 100 print_two_values
Výsledek získaný po zavolání tohoto skriptu:
100 200 100 2 1 2
Pozor je ovšem nutné dát na to, že nelze použít „pythonovský“ způsob zápisu s explicitním určením jména a hodnoty při volání:
function print_two_values(a=1, b=2) print(a, b) end print_two_values a=100
Ten povede k chybě:
Error: [string "core"]:0: cannot bind 100 to: function(a, b): 0x40912da8 stack traceback: [C]: in function 'error' [string "core"]: in function 'bind' [string "core"]: in function '__extract__' error.shn:5: in main chunk [string "shine"]: in main chunk [string "shine"]: in main chunk [C]: at 0x555ca0a40510 shell returned 1
Naproti tomu je možné – na rozdíl od Pythonu – nejprve uvést parametry s výchozí hodnotou a teprve poté běžné poziční parametry:
function print_two_values(a=1, b) print(a, b) end print_two_values 100, 200 print_two_values 100 print_two_values
Tentokrát se v posledních dvou voláních do parametru b nedosadí žádná hodnota (argument), takže se použije výchozí hodnota nil, a to bez nahlášení chyby:
100 200 100 nil 1 nil
11. Nepovinná typová kontrola
Zapomenout nesmíme asi na pravděpodobně největší potenciální přínos programovacího jazyka Shine. Nejsou to ani nové jazykové konstrukce (syntaxe), ale možnost specifikace typů (tedy spíše sémantická záležitost). Nejprve si ukažme způsob specifikace typu parametru funkce. Je to snadné a používá se zde slovo is, zatímco například v podobně koncipovaném Pythonu se používá dvojtečka:
function print_number(a is Number) print(a) end print_number(100) print_number(3.14) print_number("foo") print_number(nil)
V případě, že tento skript spustíme, vypíše se první předaný argument (ten je typu Number), taktéž druhý předaný argument (i reálná čísla jsou Number) a poté se již detekuje chyba:
100 3.14 Error: 09_type_checks.shn:7: bad argument #1 to 'print_number' (Number expected got String) stack traceback: [C]: in function 'error' 09_type_checks.shn:1: in function 'print_number' 09_type_checks.shn:7: in main chunk [string "shine"]: in main chunk [string "shine"]: in main chunk [C]: at 0x559e2c5e2510 ;
Mezi základní datové typy patří:
Jméno typu |
---|
Nil |
Boolean |
Number |
String |
Table |
Function |
Coroutine |
UserData |
CData |
null |
Array |
Range |
Error |
Class |
Module |
Grammar |
Pattern |
ArrayPattern |
TablePattern |
ApplyPattern |
Meta |
12. Specifikace typu globálních i lokálních proměnných
Naprosto stejným způsobem lze specifikovat typ globálních proměnných při jejich deklaraci:
a is Number = 10 b is String = "foo" c is Boolean = true d is Nil = nil print a print b print c print d
Nekorektní použití vypadá následovně:
a is Number = "foo" b is String = 3 c is Boolean = nil d is Nil = true print a print b print c print d
Chyba detekovaná interpretrem Shine:
Error: 11_variable_types.shn:1: bad assignment to 'a' (Number expected got String) stack traceback: [C]: in function 'error' [string "core"]: in function '__check__' 11_variable_types.shn:1: in main chunk [string "shine"]: in main chunk [string "shine"]: in main chunk [C]: at 0x560afc92d510
Ukažme si ještě způsob deklarace typů u lokálních proměnných. Zápis vypadá stejně, jako v předchozích dvou příkladech:
function test() a is Number = 10 b is String = "foo" c is Boolean = true d is Nil = nil print a print b print c print d end test
13. Viditelnost proměnných: rozdíl mezi jazykem Lua a Shine
Poměrně nepraktickou vlastností programovacího jazyka Lua je jeho způsob rozlišování globálních a lokálních proměnných. Připomeňme si, že při přiřazení hodnoty do nové proměnné je tato proměnná vytvořena. A v případě, že není použito klíčové slovo local, je nově vytvořená proměnná globální. To je vlastně přesný opak sémantiky jazyka Python, kde je naopak taková proměnná lokální.
Například následující skript zapsaný v jazyce Lua vytvoří po zavolání funkce test novou globální proměnnou a, která bude viditelná i při výskoku z této funkce:
function test() a = 10 end test() print(a)
Tento skript bude funkční a při svém spuštění vypíše hodnotu 10.
Programovací jazyk Shine má v tomto případě odlišnou sémantiku, protože proměnná a naplněná uvnitř funkce test nebude globální:
function test() a = 10 end test() print(a)
Při pokusu o spuštění tohoto skriptu vznikne chyba:
Error: [string "shine.lang.translator"]:0: shine: 13_local_variables.shn:6: "a" used but not defined stack traceback: [C]: in function 'error' [string "shine.lang.translator"]: in function 'abort' [string "shine.lang.translator"]: in function 'close' [string "shine.lang.translator"]: in function 'translate' [string "shine.lang.loader"]: in function 'loadchunk' [string "shine"]: in main chunk [string "shine"]: in main chunk [C]: at 0x561f56df3510
Lokální proměnnou lze označit klíčovým slovem local, a to i v jazyku Lua:
function test() local a = 10 end test() print(a)
Tento skript se (bohužel) dokončí bez chyby a vypíše se:
nil
Klíčové slovo local můžeme použít i ve Shine, i když zde má stejný význam, jako přiřazení do proměnné bez zápisu tohoto klíčového slova:
function test() local a = 10 end test() print(a)
Výsledkem bude (opět) chybové hlášení:
Error: [string "shine.lang.translator"]:0: shine: 14_local_variable.shn:6: "a" used but not defined stack traceback: [C]: in function 'error' [string "shine.lang.translator"]: in function 'abort' [string "shine.lang.translator"]: in function 'close' [string "shine.lang.translator"]: in function 'translate' [string "shine.lang.loader"]: in function 'loadchunk' [string "shine"]: in main chunk [string "shine"]: in main chunk [C]: at 0x55f6fdf73510
14. Globální a lokální proměnná se stejným jménem
Vyzkoušejme si chování programovacího jazyka Shine v případě, že je ve funkci deklarována nová proměnná s uvedením klíčového slova local a současně existuje stejná globální proměnná stejného jména. Zajímat nás bude, jaká hodnota se vypíše posledním příkazem print:
a = 20 function test() local a = 10 end test() print(a)
Po spuštění tohoto příkladu se podle očekávání vypíše hodnota 20.
Druhý skript je prakticky totožný, ovšem chybí zde klíčové slovo local:
a = 20 function test() a = 10 end test() print(a)
Nyní se vypíše hodnota 10. Proč tomu tak je? Jazyk Shine zjistil, že se snažíme změnit hodnotu proměnné a, která existuje a je viditelná. Proto nevytvořil proměnnou novou.
15. Nelokální proměnné a uzávěry
S problematikou viditelnosti proměnných souvisí i možnost přístupu k nelokálním proměnným, tj. k proměnným, které nejsou globální a současně jsou definovány vně nějaké funkce. Tyto proměnné se typicky používají v uzávěrech (closure). Můžeme se pokusit o vytvoření jednoduchého uzávěru, který bude pracovat jako čítač: funkce next, která je návratovou hodnotou „konstruktoru“ counter při každém svém zavolání vrátí hodnotu čítače zvýšenou o jedničku. Přitom při každém zavolání „konstruktoru“ counter získáme nový čítač nezávislý na ostatních čítačích, což znamená, že na funkci next bude navázána odlišná nelokální proměnná cnt:
function counter() local cnt = 0 function next() cnt = cnt + 1 return cnt end return next end counter1 = counter() counter2 = counter() counter2() for i = 0, 10 do result1 = counter1() result2 = counter2() print(i, result1, result2) end
Otestujme si to v praxi, nejdříve při použití interpretru jazyka Lua:
$ lua 17_counter_1.lua 0 1 2 1 2 3 2 3 4 3 4 5 4 5 6 5 6 7 6 7 8 7 8 9 8 9 10 9 10 11 10 11 12
V jazyce Shine je zápis čítače naprosto totožný:
function counter() local cnt = 0 function next() cnt = cnt + 1 return cnt end return next end counter1 = counter() counter2 = counter() counter2() for i = 0, 10 do result1 = counter1() result2 = counter2() print(i, result1, result2) end
I chování jazyka Shine je stejné – získáme dva na sobě nezávislé čítače:
$ ./shine 17_counter_1.shn 0 1 2 1 2 3 2 3 4 3 4 5 4 5 6 5 6 7 6 7 8 7 8 9 8 9 10 9 10 11 10 11 12
Navíc je možné v jazyce Shine zkrátit zápis:
cnt = cnt + 1
na:
cnt += 1
Což je provedeno v tomto skriptu:
function counter() cnt = 0 function next() cnt += 1 return cnt end return next end counter1 = counter() counter2 = counter() counter2() for i = 0, 10 do result1 = counter1() result2 = counter2() print(i, result1, result2) end
def createCounter(): counter = 0 def next(): nonlocal counter counter += 1 return counter return next def main(): counter1 = createCounter() counter2 = createCounter() for i in range(1,11): result1 = counter1() result2 = counter2() print("Iteration #%d" % i) print(" Counter1: %d" % result1) print(" Counter2: %d" % result2) main()
Jen na okraj důkaz, že se tento skript chová podle předpokladů:
Iteration #1 Counter1: 1 Counter2: 1 Iteration #2 Counter1: 2 Counter2: 2 Iteration #3 Counter1: 3 Counter2: 3 Iteration #4 Counter1: 4 Counter2: 4 Iteration #5 Counter1: 5 Counter2: 5 Iteration #6 Counter1: 6 Counter2: 6 Iteration #7 Counter1: 7 Counter2: 7 Iteration #8 Counter1: 8 Counter2: 8 Iteration #9 Counter1: 9 Counter2: 9 Iteration #10 Counter1: 10 Counter2: 10
16. Uzávěr s parametrem
V předchozí kapitole jsme si ukázali, jakým způsobem může uzávěr přistupovat k nelokální proměnné, která je jeho součástí. Ovšem totéž lze v případě potřeby provést i s nelokálním parametrem. To je ukázáno v dalším demonstračním příkladu, v němž je opět implementován jednoduchý čítač, ovšem upravený do takové podoby, že je možné parametrem delta specifikovat krok čítače, tj. numerický rozdíl mezi dvěma po sobě vygenerovanými hodnotami.
Realizace v jazyku Lua vypadá takto:
function counter(delta) local cnt = 0 function next() cnt = cnt + delta return cnt end return next end counter1 = counter(2) counter2 = counter(3) for i = 0, 10 do result1 = counter1() result2 = counter2() print(i, result1, result2) end
Výsledky z činnosti dvojice nezávislých čítačů by měly vypadat následovně:
0 2 3 1 4 6 2 6 9 3 8 12 4 10 15 5 12 18 6 14 21 7 16 24 8 18 27 9 20 30 10 22 33
Naprosto stejným způsobem lze totéž realizovat v jazyku Shine; s nepovinným použitím modifikátoru local a s možností náhrady cnt = cnt + delta za cnt += delta:
function counter(delta) local cnt = 0 function next() cnt = cnt + delta return cnt end return next end counter1 = counter(2) counter2 = counter(3) for i = 0, 10 do result1 = counter1() result2 = counter2() print(i, result1, result2) end
I zobrazené výsledky budou totožné:
0 2 3 1 4 6 2 6 9 3 8 12 4 10 15 5 12 18 6 14 21 7 16 24 8 18 27 9 20 30 10 22 33
17. Obsah druhé části článku
Ve druhé části článku o programovacím jazyku Shine nejprve dokončíme popis všech vylepšení a rozdílů mezi Shine a původním jazykem Lua. Posléze si ukážeme, jaké možnosti vývojářům nabízí překladač shinec.
18. Repositář s demonstračními příklady
Demonstrační příklady popsané v dnešním článku byly uloženy do veřejného Git repositáře, z něhož si je můžete snadno stáhnout a otestovat ve své instalaci jazyka Shine:
19. Odkazy na relevantní články a seriály na Rootu
S technologiemi souvisejícími s programovacím jazykem Lua, LuaJITem, ale i s jazyky postavenými nad ekosystémem Luy (viz například výše zmíněný Moonscript) jsme se již na stránkách Roota několikrát setkali. Následují odkazy na více či méně relevantní články k dnes probíranému tématu:
- Seriál Programovací jazyk Lua
https://www.root.cz/serialy/programovaci-jazyk-lua/ - Seriál Torch: framework pro strojové učení
https://www.root.cz/serialy/torch-framework-pro-strojove-uceni/ - Skriptovací jazyk Lua v aplikacích naprogramovaných v Go
https://www.root.cz/clanky/skriptovaci-jazyk-lua-v-aplikacich-naprogramovanych-v-go/ - 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/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua
https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua (2)
http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-2/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua (3)
http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-3/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua (4)
http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-4/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua (5 – tabulky a pole)
http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-5-tabulky-a-pole/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua (6 – překlad programových smyček do mezijazyka LuaJITu)
http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-6-preklad-programovych-smycek-do-mezijazyka-luajitu/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua (7 – dokončení popisu mezijazyka LuaJITu)
http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-7-dokonceni-popisu-mezijazyka-luajitu/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua (8 – základní vlastnosti trasovacího JITu)
http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-8-zakladni-vlastnosti-trasovaciho-jitu/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua (9 – další vlastnosti trasovacího JITu)
http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-9-dalsi-vlastnosti-trasovaciho-jitu/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua (10 – JIT překlad do nativního kódu)
http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-10-jit-preklad-do-nativniho-kodu/ - Moonscript: jazyk inspirovaný CoffeeScriptem určený pro ekosystém jazyka Lua
https://www.root.cz/clanky/moonscript-jazyk-inspirovany-coffeescriptem-urceny-pro-ekosystem-jazyka-lua/ - Moonscript: jazyk inspirovaný CoffeeScriptem určený pro ekosystém jazyka Lua (2)
https://www.root.cz/clanky/moonscript-jazyk-inspirovany-coffeescriptem-urceny-pro-ekosystem-jazyka-lua-2/ - Moonscript: jazyk inspirovaný CoffeeScriptem určený pro ekosystém jazyka Lua (dokončení)
https://www.root.cz/clanky/moonscript-jazyk-inspirovany-coffeescriptem-urceny-pro-ekosystem-jazyka-lua-dokonceni/ - Použití nástroje RQ (Redis Queue) pro správu úloh zpracovávaných na pozadí
https://www.root.cz/clanky/pouziti-nastroje-rq-redis-queue-pro-spravu-uloh-zpracovavanych-na-pozadi/ - Proudy (streams) podporované systémem Redis
https://www.root.cz/clanky/proudy-streams-podporovane-systemem-redis/ - Proudy (streams) podporované systémem Redis (dokončení)
https://www.root.cz/clanky/proudy-streams-podporovane-systemem-redis-dokonceni/
20. Odkazy na Internetu
- Repositář projektu Shine
https://github.com/richardhundt/shine - Languages that compile to Lua
https://github.com/hengestone/lua-languages?tab=readme-ov-file#languages-that-compile-to-lua - Repositář projektu Lua Fun
https://github.com/luafun/luafun - Lua Functional 0.1.3 documentation
https://luafun.github.io/reference.html - Lua Profiler (GitHub)
https://github.com/luaforge/luaprofiler - Lua Profiler (LuaForge)
http://luaforge.net/projects/luaprofiler/ - ctrace
http://webserver2.tecgraf.puc-rio.br/~lhf/ftp/lua/ - The Lua VM, on the Web
https://kripken.github.io/lua.vm.js/lua.vm.js.html - Lua.vm.js REPL
https://kripken.github.io/lua.vm.js/repl.html - lua2js
https://www.npmjs.com/package/lua2js - lua2js na GitHubu
https://github.com/basicer/lua2js-dist - Lua (programming language)
http://en.wikipedia.org/wiki/Lua_(programming_language) - LuaJIT 2.0 SSA IR
http://wiki.luajit.org/SSA-IR-2.0 - The LuaJIT Project
http://luajit.org/index.html - LuaJIT FAQ
http://luajit.org/faq.html - LuaJIT Performance Comparison
http://luajit.org/performance.html - LuaJIT 2.0 intellectual property disclosure and research opportunities
http://article.gmane.org/gmane.comp.lang.lua.general/58908 - LuaJIT Wiki
http://wiki.luajit.org/Home - LuaJIT 2.0 Bytecode Instructions
http://wiki.luajit.org/Bytecode-2.0 - Programming in Lua (first edition)
http://www.lua.org/pil/contents.html - Lua 5.2 sources
http://www.lua.org/source/5.2/ - REPL
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - The LLVM Compiler Infrastructure
http://llvm.org/ProjectsWithLLVM/ - clang: a C language family frontend for LLVM
http://clang.llvm.org/ - LLVM Backend („Fastcomp“)
http://kripken.github.io/emscripten-site/docs/building_from_source/LLVM-Backend.html#llvm-backend - Lambda the Ultimate: Coroutines in Lua,
http://lambda-the-ultimate.org/node/438 - Coroutines Tutorial,
http://lua-users.org/wiki/CoroutinesTutorial - Lua Coroutines Versus Python Generators,
http://lua-users.org/wiki/LuaCoroutinesVersusPythonGenerators