Hlavní navigace

Jazyk Shine: „lepší“ Lua s novými jazykovými konstrukcemi a vylepšeními

2. 4. 2024
Doba čtení: 27 minut

Sdílet

 Autor: Depositphotos
V dnešním článku se seznámíme se základními vlastnostmi jazyka Shine. Ten je postaven na známém a poměrně populárním jazyku Lua i na ekosystému LuaJITu. Výsledek je zajímavý a dokonce i prakticky použitelný.

Obsah

1. Programovací jazyk Lua: stálice na poli programovacích jazyků

2. LuaJIT

3. Lua je stabilním jazykem, pro některé účely však až příliš stabilním

4. Od jazyku Lua ke Shine

5. Překlad jazyka Shine

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

11. Nepovinná typová kontrola

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

16. Uzávěr s parametrem

17. Obsah druhé části článku

18. Repositář s demonstračními příklady

19. Odkazy na relevantní články a seriály na Rootu

20. Odkazy na Internetu

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.

Poznámka: připomeňme si, že jsme si taktéž popsali programovací jazyk Moonscript, který do značné míry využívá ekosystém vybudovaný okolo jazyka Lua (ve skutečnosti je přímo transpilovaný do Luy). Odkazy na příslušné články jsou uvedeny v předposlední kapitole.

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.

Poznámka: jazyk Shine není ve skutečnosti žádnou žhavou novinkou. Jeho vývoj byl zahájen přibližně před deseti lety (2013) a v současnosti se zdá, že jeho aktivní vývoj dále neprobíhá (což je možná velká škoda). I tak však může být užitečný, zejména v oblasti malých mikropočítačů a u aplikací, kde „čistá“ Lua nemusí dostačovat a Python je příliš těžkotonážní řešení.

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!"
Poznámka: středníky ukončující příkazy jsou nepovinné a většinou se nikdo nenamáhá s jejich použitím.

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
Poznámka: interpret shine dokáže do značné míry emulovat chování klasického interpretru jazyka Lua. To v praxi znamená, že pokud se pokusíme o spuštění původního skriptu s koncovkou „.lua“, i shine vypíše chybu:
$ ./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
Poznámka: informace o jednotlivých vybraných datových typech bude uvedena příště.

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
Poznámka: to je v praxi a u větších aplikací velmi problematické chování.

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.

Poznámka: odlišná sémantika viditelnosti proměnných je jedním z největších rozdílů mezi jazyky Lua a Shine a je zapotřebí si na ni dávat pozor.

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
Poznámka: v Pythonu by na tomto místě bylo nutné použít klíčové slovo nonlocal pro specifikaci, že se bude přistupovat k nelokální proměnné a že se tedy nemá vytvořit nová čistě lokální proměnná:
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.

CS24 tip temata

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:

# Soubor Stručný popis Odkaz
1 01_hello_world.lua program typu „Hello, world“ ve variantě pro programovací jazyky Lua i Shine https://github.com/tisnik/pre­sentations/blob/master/shi­ne/01_hello_world.lua
2 02_hello_world.lua program typu „Hello, world“ ve variantě pro programovací jazyky Lua i Shine https://github.com/tisnik/pre­sentations/blob/master/shi­ne/02_hello_world.lua
       
3 03_print_values.lua volání funkcí s větším počtem parametrů, varianta pro jazyk Lua https://github.com/tisnik/pre­sentations/blob/master/shi­ne/03_print_values.lua
4 03_print_values.shn volání funkcí s větším počtem parametrů, varianta pro jazyk Shine https://github.com/tisnik/pre­sentations/blob/master/shi­ne/03_print_values.shn
       
5 04_comments.lua zápis komentářů do programového kódu, varianta pro jazyk Lua https://github.com/tisnik/pre­sentations/blob/master/shi­ne/04_comments.lua
6 04_comments.shn zápis komentářů do programového kódu, varianta pro jazyk Shine https://github.com/tisnik/pre­sentations/blob/master/shi­ne/04_comments.shn
7 05_comments.shn zápis komentářů do programového kódu, varianta pro jazyk Shine https://github.com/tisnik/pre­sentations/blob/master/shi­ne/05_comments.shn
       
8 06_function_call.lua definice a volání funkcí, varianta pro jazyk Lua https://github.com/tisnik/pre­sentations/blob/master/shi­ne/06_function_call.lua
9 06_function_call.shn definice a volání funkcí, varianta pro jazyk Shine https://github.com/tisnik/pre­sentations/blob/master/shi­ne/06_function_call.shn
       
10 07_default_values.shn výchozí hodnoty parametrů funkcí, první varianta https://github.com/tisnik/pre­sentations/blob/master/shi­ne/07_default_values.shn
11 08_default_values.shn výchozí hodnoty parametrů funkcí, druhá varianta https://github.com/tisnik/pre­sentations/blob/master/shi­ne/08_default_values.shn
       
12 09_type_checks.shn specifikace typů parametrů funkcí https://github.com/tisnik/pre­sentations/blob/master/shi­ne/09_type_checks.shn
13 10_variable_types.shn specifikace typů proměnných, korektní typy https://github.com/tisnik/pre­sentations/blob/master/shi­ne/10_variable_types.shn
14 11_variable_types.shn specifikace typů proměnných, nekorektní typy https://github.com/tisnik/pre­sentations/blob/master/shi­ne/11_variable_types.shn
15 12_variable_types_in_function.shn specifikace typů parametrů lokálních proměnných https://github.com/tisnik/pre­sentations/blob/master/shi­ne/12_variable_types_in_fun­ction.shn
       
16 13_local_variables.lua globální vs. lokální proměnné, varianta pro jazyk Lua https://github.com/tisnik/pre­sentations/blob/master/shi­ne/13_local_variables.lua
17 13_local_variables.shn globální vs. lokální proměnné, varianta pro jazyk Shine https://github.com/tisnik/pre­sentations/blob/master/shi­ne/13_local_variables.shn
18 14_local_variable.shn lokální proměnná, použití modifikátoru local https://github.com/tisnik/pre­sentations/blob/master/shi­ne/14_local_variable.shn
19 14_local_variables.lua lokální proměnná, použití modifikátoru local https://github.com/tisnik/pre­sentations/blob/master/shi­ne/14_local_variables.lua
20 15_global_and_local_variable.shn globální i lokální proměnná stejného jména, varianta s local https://github.com/tisnik/pre­sentations/blob/master/shi­ne/15_global_and_local_va­riable.shn
21 16_global_and_local_variable.shn globální i lokální proměnná stejného jména, varianta bez local https://github.com/tisnik/pre­sentations/blob/master/shi­ne/16_global_and_local_va­riable.shn
       
22 17_counter1.lua čítač vytvořený s využitím uzávěru, varianta pro jazyk Lua https://github.com/tisnik/pre­sentations/blob/master/shi­ne/17_counter1.lua
23 17_counter1.shn čítač vytvořený s využitím uzávěru, varianta pro jazyk Shine https://github.com/tisnik/pre­sentations/blob/master/shi­ne/17_counter1.shn
24 18_counter1.shn zjednodušený zápis předchozího příkladu https://github.com/tisnik/pre­sentations/blob/master/shi­ne/18_counter1.shn
25 19_counter2.lua čítač s konfigurovatelným krokem, varianta pro jazyk Lua https://github.com/tisnik/pre­sentations/blob/master/shi­ne/19_counter2.lua
26 19_counter2.shn čítač s konfigurovatelným krokem, varianta pro jazyk Shine https://github.com/tisnik/pre­sentations/blob/master/shi­ne/19_counter2.shn
       
27 20_varargs.shn zpracování funkce s proměnným počtem parametrů https://github.com/tisnik/pre­sentations/blob/master/shi­ne/20_varargs.shn
28 21_varargs.shn zpracování funkce s proměnným počtem parametrů https://github.com/tisnik/pre­sentations/blob/master/shi­ne/21_varargs.shn
       
29 22_table_type.shn parametr funkce typu table https://github.com/tisnik/pre­sentations/blob/master/shi­ne/22_table_type.shn
30 23_array_type.shn parametr funkce typu array https://github.com/tisnik/pre­sentations/blob/master/shi­ne/23_array_type.shn

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:

  1. Seriál Programovací jazyk Lua
    https://www.root.cz/seria­ly/programovaci-jazyk-lua/
  2. Seriál Torch: framework pro strojové učení
    https://www.root.cz/serialy/torch-framework-pro-strojove-uceni/
  3. Skriptovací jazyk Lua v aplikacích naprogramovaných v Go
    https://www.root.cz/clanky/skriptovaci-jazyk-lua-v-aplikacich-naprogramovanych-v-go/
  4. 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/
  5. LuaJIT – Just in Time překladač pro programovací jazyk Lua
    https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua/
  6. 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/
  7. 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/
  8. 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/
  9. 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/
  10. 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/
  11. 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/
  12. 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/
  13. 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/
  14. 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/
  15. 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/
  16. 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/
  17. 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/
  18. 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/
  19. Proudy (streams) podporované systémem Redis
    https://www.root.cz/clanky/proudy-streams-podporovane-systemem-redis/
  20. Proudy (streams) podporované systémem Redis (dokončení)
    https://www.root.cz/clanky/proudy-streams-podporovane-systemem-redis-dokonceni/

20. Odkazy na Internetu

  1. Repositář projektu Shine
    https://github.com/richardhundt/shine
  2. Languages that compile to Lua
    https://github.com/hengestone/lua-languages?tab=readme-ov-file#languages-that-compile-to-lua
  3. Repositář projektu Lua Fun
    https://github.com/luafun/luafun
  4. Lua Functional 0.1.3 documentation
    https://luafun.github.io/re­ference.html
  5. Lua Profiler (GitHub)
    https://github.com/luafor­ge/luaprofiler
  6. Lua Profiler (LuaForge)
    http://luaforge.net/projec­ts/luaprofiler/
  7. ctrace
    http://webserver2.tecgraf.puc-rio.br/~lhf/ftp/lua/
  8. The Lua VM, on the Web
    https://kripken.github.io/lu­a.vm.js/lua.vm.js.html
  9. Lua.vm.js REPL
    https://kripken.github.io/lu­a.vm.js/repl.html
  10. lua2js
    https://www.npmjs.com/package/lua2js
  11. lua2js na GitHubu
    https://github.com/basicer/lua2js-dist
  12. Lua (programming language)
    http://en.wikipedia.org/wi­ki/Lua_(programming_langu­age)
  13. LuaJIT 2.0 SSA IR
    http://wiki.luajit.org/SSA-IR-2.0
  14. The LuaJIT Project
    http://luajit.org/index.html
  15. LuaJIT FAQ
    http://luajit.org/faq.html
  16. LuaJIT Performance Comparison
    http://luajit.org/performance.html
  17. LuaJIT 2.0 intellectual property disclosure and research opportunities
    http://article.gmane.org/gma­ne.comp.lang.lua.general/58908
  18. LuaJIT Wiki
    http://wiki.luajit.org/Home
  19. LuaJIT 2.0 Bytecode Instructions
    http://wiki.luajit.org/Bytecode-2.0
  20. Programming in Lua (first edition)
    http://www.lua.org/pil/contents.html
  21. Lua 5.2 sources
    http://www.lua.org/source/5.2/
  22. REPL
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  23. The LLVM Compiler Infrastructure
    http://llvm.org/ProjectsWithLLVM/
  24. clang: a C language family frontend for LLVM
    http://clang.llvm.org/
  25. LLVM Backend („Fastcomp“)
    http://kripken.github.io/emscripten-site/docs/building_from_source/LLVM-Backend.html#llvm-backend
  26. Lambda the Ultimate: Coroutines in Lua,
    http://lambda-the-ultimate.org/node/438
  27. Coroutines Tutorial,
    http://lua-users.org/wiki/CoroutinesTutorial
  28. Lua Coroutines Versus Python Generators,
    http://lua-users.org/wiki/LuaCorouti­nesVersusPythonGenerators

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.