Na úvodní článek o typovém systému a taktéž o statických typových kontrolách realizovaných v projektu Luau dnes navážeme a celé téma dokončíme. Vysvětlíme si další podrobnosti o typovém systému (který se v několika ohledech liší od například Pythonu s Mypy), budeme se zabývat hierarchií datových typů a nezapomeneme například ani na definici generických funkcí (a metod), na funkce, které jsou spouštěny v době statické typové analýzy (a tedy ne v runtime) atd.
Co se dozvíte v článku
- Funkce type a typeof
- Hierarchie datových typů
- Sjednocení typů
- Problematický případ a jeho řešení
- Průnik typů
- Příklady použití průniku typů
- Specifikace typu „funkce“
- Praktické příklady specifikací typu „funkce“ u proměnných
- Komplikovanější příklady specifikace typu „funkce“
- Rozdíl mezi jazykem Lua a Pythonem u funkcí vracejících „nic“
- Nepovinné parametry funkcí
- Od běžných funkcí k funkcím generickým
- Definice nových datových typů
- Funkce odpovídající zapsanému typu
- Funkce spouštěné v době statické typové analýzy
- Sjednocené typy se jmenovkou
- Závěr
- Repositář s demonstračními příklady
- Odkazy na Internetu
Všechny dnes uvedené demonstrační příklady je možné spustit přímo v interpretru luau popř. provést jejich statickou analýzu nástrojem luau-analyze. Způsob překladu těchto nástrojů byl vysvětlen minule v této kapitole.
Obrázek 1: Logo programovacího jazyka Luau už příliš nepřipomíná Měsíc obíhající okolo Země.
Funkce type a typeof
Pro začátek začněme velmi jednoduchým rozšířením programovacího jazyka Luau. Připomeňme si, že v původním („originálním“) jazyku Lua je již v základní knihovně k dispozici funkce nazvaná type, která pro libovolnou hodnotu vrátí jméno typu této hodnoty (jako řetězec). Tato funkce pracuje se všemi typy, včetně nil atd., což jsme si ostatně již otestovali v úvodním článku:
v1 = nil
v2 = true
v3 = 123
v4 = 0.5
v5 = "hello"
v6 = {1, 2, 3}
v7 = function() end
v8 = coroutine.create(function() end)
v9 = newproxy and newproxy(true) or nil
v10 = buffer and buffer.create(1) or nil
print("type(v1)=", type(v1))
print("type(v2)=", type(v2))
print("type(v3)=", type(v3))
print("type(v4)=", type(v4))
print("type(v5)=", type(v5))
print("type(v6)=", type(v6))
print("type(v7)=", type(v7))
print("type(v8)=", type(v8))
print("type(v9)=", type(v9))
print("type(v10)=", type(v10))
Snadno si můžeme otestovat, že funkce type je stále k dispozici:
$ ./luau runtime_types.lua type(v1)= nil type(v2)= boolean type(v3)= number type(v4)= number type(v5)= string type(v6)= table type(v7)= function type(v8)= thread type(v9)= userdata type(v10)= buffer
V jazyku Luau byla k funkci type přidána i její alternativa nazvaná typeof. I tato funkce vrací řetězec s názvem typu a většinou se bude jednat o stejné řetězce, jako v případě funkce type. Rozdíl bude možné zaznamenat zejména u typu vector (dostupný například v Robloxu): funkce type vrátí pouze řetězec „vector“, zatímco funkce typeof zjistí, kolik prvků (dimenzí) tento vektor má a vrátí konkrétní hodnotu.
Otestujme si chování funkce typeof pouze v interpretru Luau (tedy nikoli v prostředí Robloxu):
v1 = nil
v2 = true
v3 = 123
v4 = 0.5
v5 = "hello"
v6 = {1, 2, 3}
v7 = function() end
v8 = coroutine.create(function() end)
v9 = newproxy and newproxy(true) or nil
v10 = buffer and buffer.create(1) or nil
print("typeof(v1)=", typeof(v1))
print("typeof(v2)=", typeof(v2))
print("typeof(v3)=", typeof(v3))
print("typeof(v4)=", typeof(v4))
print("typeof(v5)=", typeof(v5))
print("typeof(v6)=", typeof(v6))
print("typeof(v7)=", typeof(v7))
print("typeof(v8)=", typeof(v8))
print("typeof(v9)=", typeof(v9))
print("typeof(v10)=", typeof(v10))
Zprávy, které se vypíšou, by měly být stejné, jako v případě funkce type:
$ ./luau typeof.lua typeof(v1)= nil typeof(v2)= boolean typeof(v3)= number typeof(v4)= number typeof(v5)= string typeof(v6)= table typeof(v7)= function typeof(v8)= thread typeof(v9)= userdata typeof(v10)= buffer
Hierarchie datových typů
V typovém systému programovacího jazyka Luau nalezneme mj. i typ any (byl použit minule) a na druhé straně spektra typ never. Typ any se nazývá top, protože každá hodnota tomuto typu odpovídá. Naproti tomu typu never neodpovídá žádná hodnota a proto se nazývá bottom. Pod typem any se nachází typ unknown, který se liší tím, že při přístupu k proměnné se musí použít konverze nebo kontrola typu. Následně v hierarchii najdeme běžné (primitivní) typy (pravdivostní hodnota, číslo, řetězec, nil), dále tabulky a funkce, sjednocení typů (union) a průnik typů (intersection), které budou vysvětleny v dalším textu:
any
|
unknown
|
-----------------------
| | |
types unions intersections
| | |
... ... ...
|
never
Sjednocení typů
Nové datové typy mohou v jazyku Luau vzniknout sjednocením existujících typů. Sjednocení neboli union se zapisuje pomocí operátoru |. Podívejme se nyní na příklad použití. V následujícím příkladu je definována proměnná x takovým způsobem, že může obsahovat pouze řetězce. Přiřazení hodnoty odlišného typu je při provádění statické typové analýzy ohlášeno jako chyba:
--!strict local x: string = "foo" x = 42 print(x)
Statická typová analýza:
$ ./luau-analyze union_type_1.lua ./union_type_1.lua(5,5): TypeError: Expected this to be 'string', but got 'number'
V případě, že skutečně budeme vyžadovat, aby proměnná mohla obsahovat buď řetězce nebo numerické hodnoty, použijeme sjednocení typů:
--!strict local x: string | number = "foo" x = 42 print(x)
Totéž platí u specifikace typů parametrů funkcí a/nebo jejich návratových hodnot. I následující kód je z pohledu typového systému korektní:
--!strict
function swap(x: number | string | boolean, y: number):(number, number | string | boolean)
return y, x
end
print(swap(1, 2))
print(swap("foo", 2))
print(swap(true, 2))
Typové kontroly jsou prováděny i při přiřazování výsledků, která funkce vrací, do proměnných. V dalším demonstračním příkladu jsem si dal pozor na to, aby byla vrácená hodnota přiřazena do funkce s korektním typem:
--!strict
function swap(x: number | string | boolean, y: number):(number, number | string | boolean)
return y, x
end
local x1: number = 0
local y1: number = 0
x1, y1 = swap(1, 2)
print(x1, y1)
local x2: number = 0
local y2: string = ""
x2, y2 = swap("foo", 2)
print(x2, y2)
local x3: number = 0
local y3: boolean = false
x3, y3 = swap(true, 2)
print(x3, y3)
Problematický případ a jeho řešení
V některých případech je ovšem striktní statická typová kontrola zbytečně přísná a chová se nekorektně. Příkladem může být funkce foo, která vrací obsah svého parametru. Teoreticky je tedy i v době provádění statické typové analýzy zřejmé, jakého typu bude výsledek, ovšem ve skutečnosti dostaneme varování o nekorektním použití návratové hodnoty:
--!strict
function foo(x: number | string | boolean):(number | string | boolean)
return x
end
local x: number = foo(0)
local y: string = foo("foo")
local z: boolean = foo(true)
print(x, y, z)
Výsledky statické typové kontroly v tomto případě oznamují, že typy proměnných x, y a z nejsou dostatečně „široké“:
$ ./luau-analyze union_type_5.lua
./union_type_5.lua(7,19): TypeError: Expected this to be 'number', but got 'boolean | number | string';
this is because
* the 2nd component of the union is `string`, which is not a subtype of `number`
* the 3rd component of the union is `boolean`, which is not a subtype of `number`
./union_type_5.lua(8,19): TypeError: Expected this to be 'string', but got 'boolean | number | string';
this is because
* the 1st component of the union is `number`, which is not a subtype of `string`
* the 3rd component of the union is `boolean`, which is not a subtype of `string`
./union_type_5.lua(9,20): TypeError: Expected this to be 'boolean', but got 'boolean | number | string';
this is because
* the 1st component of the union is `number`, which is not a subtype of `boolean`
* the 2nd component of the union is `string`, which is not a subtype of `boolean`
Řešení je (tedy kromě vypnutí striktních typových kontrol) sjednocení typu proměnné x:
--!strict
function foo(x: number | string | boolean):(number | string | boolean)
return x
end
local x: number | string | boolean = foo(0)
print(type(x))
x = foo("foo")
print(type(x))
x = foo(true)
print(type(x))
Nyní již vše proběhne v pořádku:
$ ./luau-analyze union_type_6.lua
Průnik typů
V typovém systému programovacího jazyka Luau je možné nový datový typ získat kromě sjednocení (union) i průnikem (intersection) existujících typů. Průnik vlastně omezuje množinu možných hodnot. Vzhledem k tomu, nad jakým programovacím jazykem je typový systém postaven (neexistence třídního OOP) je průnik prakticky použitelný pouze pro tabulky. Podívejme se na jednoduchý demonstrační příklad, ve kterém jsou definovány dva nové datové typy nazvané XCoord a YCoord:
type XCoord = {x: number}
type YCoord = {y: number}
Co to ovšem přesně znamená? Typ XCoord popisuje jakoukoli tabulku, pro níž platí jediné omezení – musí obsahovat prvek s klíčem x, hodnota prvku musí být typu number. Může ovšem obsahovat i další prvky. A pro typ YCoord platí, že se jedná o tabulku, která musí obsahovat prvek y, taktéž typu number. Může ovšem obsahovat i libovolné další prvky.
Sjednocením:
type Vector2 = XCoord & YCoord
získáme nový datový typ, který omezuje možnosti tabulek ještě více: tabulky typu Vector2 musí obsahovat jak prvek s klíčem x, tak i prvek s klíčem y. Oba tyto prvky musí být typu number. A opět platí – tabulky mohou obsahovat i libovolné další prvky.
Příklady použití průniku typů
Otestujme si některé příklady, ve kterých je nový datový typ definován formou průniku několika existujících datových typů. V prvním příkladu je definován typ Vector2 na základě dvojice existujících typů XCoord a YCoord:
--!strict
type XCoord = {x: number}
type YCoord = {y: number}
type Vector2 = XCoord & YCoord
local vec2: Vector2 = {x = 1, y = 2}
print(vec2.x, vec2.y)
Příklad je plně funkční a statická typová analýza v něm nenalezne žádné chyby:
$ ./luau-analyze intersection_type_1.lua
Díky tomu, že průnikem dvou typů vznikne nový plnohodnotný datový typ, je možné tento typ použít ve specifikaci dalšího průniku. To je ukázáno v následujícím demonstračním příkladu s definicí datového typu Vector3, který představuje tříprvkový vektor, tj. tabulku obsahující povinné prvky x, y a z (a libovolné množství nepovinných prvků):
--!strict
type XCoord = {x: number}
type YCoord = {y: number}
type ZCoord = {z: number}
type Vector2 = XCoord & YCoord
type Vector3 = XCoord & YCoord & ZCoord
local vec2: Vector2 = {x = 1, y = 2}
local vec3: Vector3 = {x = 1, y = 2, z = 3}
print(vec2.x, vec2.y)
print(vec3.x, vec3.y, vec3.z)
Ani v tomto případě statická typová analýza nenalezne žádné chyby:
$ ./luau-analyze intersection_type_2.lua
Chyba je ovšem zanesena do dalšího příkladu. Konkrétně se jedná o řádek s definicí a inicializací proměnné vec3, protože jsme „zapomněli“ inicializovat prvek x:
--!strict
type XCoord = {x: number}
type YCoord = {y: number}
type ZCoord = {z: number}
type Vector2 = XCoord & YCoord
type Vector3 = XCoord & YCoord & ZCoord
local vec2: Vector2 = {x = 1, y = 2}
local vec3: Vector3 = {y = 2, z = 3}
print(vec2.x, vec2.y)
print(vec3.x, vec3.y, vec3.z)
Výsledky statické typové analýzy:
$ ./luau-analyze intersection_type_3.lua
./intersection_type_3.lua(11,23): TypeError: Table type '{ y: number, z: number
}' not compatible with type '{ x: number, y: number, z: number }' because the
former is missing field 'x'
Otestujme si, jak typový systém programovacího jazyka Luau vyhodnotí situaci, v níž máme definovány dva datové typy „tabulka“, přičemž každý z těchto typů vyžaduje stejný prvek y. Bude možné z těchto dvou typů vytvořit průnik? Možné to bude, ovšem jen za předpokladu, že oba stejně pojmenované prvky jsou i stejného typu (tedy v tomto případě konkrétně typu number):
--!strict
type Coord1 = {x: number, y: number}
type Coord2 = {y: number, z: number}
type Vector3 = Coord1 & Coord2
local vec3: Vector3 = {x = 1, y = 2, z = 3}
print(vec3.x, vec3.y, vec3.z)
Kontrola korektnosti:
$ ./luau-analyze intersection_type_4.lua
A konečně otestujeme podobný případ, který se ovšem liší tím, že prvek y má být pokaždé jiného typu:
--!strict
type Coord1 = {x: number, y: boolean}
type Coord2 = {y: number, z: number}
type Vector3 = Coord1 & Coord2
local vec3: Vector3 = {x = 1, y = 2, z = 3}
print(vec3.x, vec3.y, vec3.z)
V tomto případě statická typová kontrola (podle očekávání) zjistí, že nelze provést bezchybný průnik typů Coord1 a Coord2:
$ ./luau-analyze intersection_type_5.lua
./intersection_type_5.lua(8,23): TypeError: Expected this to be
'Coord1 & Coord2'
but got
'{ x: number, y: number, z: number }';
accessing `y` results in `number` and in the 1st component of the intersection, accessing `y` results in `boolean`, and `number` is not exactly `boolean`
Specifikace typu „funkce“
Připomeňme si, že funkce jsou v programovacím jazyce Lua (a tudíž i v jazyce Luau) plnohodnotným datovým typem. To mj. znamená, že je možné pracovat i s typem „funkce“. Ovšem nejedná se o typ „libovolná funkce“, protože je nutné specifikovat počet a typ parametrů i počet a typ návratových hodnot. V samotné specifikaci typu funkce se typy parametrů i typy návratových hodnot zapisují do kulatých závorek a jsou odděleny šipkou doprava ->. Konkrétně zápis typů může vypadat takto:
| Zápis typu | Stručný popis |
|---|---|
| (typ1) → (typ2) | funkce s jediným parametrem typ1 a jedinou návratovou hodnotou typ2 |
| (typ1) → typ2 | (vynechané druhé závorky) funkce s jediným parametrem typ1 a jedinou návratovou hodnotou typ2 |
| (typ1, typ2) → (typ3) | funkce se dvěma parametry určeného typu a jedinou návratovou hodnotou |
| (typ1, typ2) → (typ3, typ4) | funkce se dvěma parametry určeného typu a dvojicí návratových hodnot |
| (typ1, typ2) → () | funkce se dvěma parametry určeného typu, ovšem bez návratové hodnoty |
| () → (typ1) | funkce bez parametrů s jednou návratovou hodnotou |
| () → () | funkce bez parametrů i bez návratové hodnoty |
Praktické příklady specifikací typu „funkce“ u proměnných
Otestujme si nyní, jak se vlastně typ „funkce“ zapisuje a používá. V dalším demonstračním příkladu je definována trojice funkcí, které sice provádí odlišné operace, ale všechny tři funkce mají totožný počet a typ parametrů i počet a typ návratových hodnot. Konkrétně všechny tři funkce akceptují jeden parametr typu číslo (number) a taktéž vrací (jediné) číslo. To znamená, že všechny tyto funkce (resp. reference na ně) je možné přiřadit do proměnné s tímto typem:
local x: (number) -> number
Celý příklad vypadá následovně:
--!strict
function zero(x: number): number
return 0
end
function inc(x: number): number
return x+1
end
function dec(x: number): number
return x-1
end
local x: (number) -> number
x = zero
print(x(42))
x = inc
print(x(42))
x = dec
print(x(42))
Spuštěním příkladu si ověříme, že x pokaždé obsahuje odlišnou referenci:
$ ./luau function_type_1.lua 0 43 41
Statická typová kontrola by neměla nalézt žádné chyby:
$ ./luau-analyze function_type_1.lua
Ve druhém demonstračním příkladu ovšem schválně změníme typ návratové hodnoty funkce inc i typ parametru funkce dec. Chyb je zde ovšem ještě více – jak v těle obou zmíněných funkcí, tak i v dalším kódu v přiřazení:
--!strict
function zero(x: number): number
return 0
end
function inc(x: number): string
return x+1
end
function dec(x: bool): number
return x-1
end
local x: (number) -> number
x = zero
print(x(42))
x = inc
print(x(42))
x = dec
print(x(42))
Statická typová analýza tyto chyby korektně nalezne a ohlásí:
$ ./luau-analyze function_type_2.lua
./function_type_2.lua(8,12): TypeError: Expected this to be 'string', but got 'number'
./function_type_2.lua(11,17): TypeError: Unknown type 'bool'
./function_type_2.lua(12,12): TypeError: Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub
./function_type_2.lua(20,5): TypeError: Expected this to be
'(number) -> number'
but got
'(number) -> string';
it returns the 1st entry in the type pack is `string` in the latter type and `number` in the former type, and `string` is not a subtype of `number`
Komplikovanější příklady specifikace typu „funkce“
Deklarace funkcí může být pochopitelně i komplikovanější. Často se setkáme s funkcemi bez návratové hodnoty, které se v typovém systému popisují následovně:
(typ1, typ2, ...) -> ()
Následuje příklad použití proměnné x, které je přiřazena reference na funkci:
--!strict
function no_return_value(x: number)
print("no_return_value called with", x)
end
local x: (number) -> ()
x = no_return_value
x(42)
Otestování:
$ ./luau function_type_3.lua no_return_value called with 42
Statická typová kontrola by neměla nalézt žádný problém:
$ ./luau-analyze function_type_3.lua
Pokud ovšem typ funkce obsahuje specifikaci typu návratové hodnoty, musí přiřazovaná funkce skutečně nějakou hodnotu vracet:
--!strict
function no_return_value(x: number)
print("no_return_value called with", x)
end
local x: (number) -> (number)
x = no_return_value
x(42)
Tento skript je sice možné spustit a proběhne bez chyby:
$ ./luau function_type_4.lua no_return_value called with 42
Ovšem statická typová kontrola odhalí, že proměnná x má odlišný typ, než je typ přiřazované funkce (funkce nevrací žádnou hodnotu):
$ ./luau-analyze function_type_4.lua
./function_type_4.lua(9,5): TypeError: Expected this to be
'(number) -> number'
but got
'(number) -> ()';
it returns `()` in the latter type and `number` in the former type, and `()` is not a subtype of `number`
Následuje příklad s funkcí vracející dvě hodnoty a definicí proměnné stejného typu, jaký má tato funkce:
--!strict
function swap(x: number, y: string):(string, number)
return y, x
end
local x: (number, string) -> (string, number)
x = swap
print(x(42, "foo"))
Tento skript je zapsán korektně a taktéž se v čase běhu korektně chová:
$ ./luau function_type_5.lua foo 42
$ ./luau-analyze function_type_5.lua
V dalším demonstračním příkladu je naschvál udělána chyba: v definici typu proměnné x jsou prohozeny typy parametrů funkce i typy návratových hodnot:
--!strict
function swap(x: number, y: string):(string, number)
return y, x
end
local x: (string, number) -> (number, string)
x = swap
print(x(42, "foo"))
Tento skript se sice po spuštění chová zdánlivě bezchybně, ovšem statická typová analýza odhalí potenciální problémy:
$ ./luau function_type_6.lua foo 42
V tomto případě je nástroj pro statickou typovou analýzu poměrně „ukecaný“:
$ ./luau-analyze function_type_6.lua
./function_type_6.lua(9,5): TypeError: Expected this to be
'(string, number) -> (number, string)'
but got
'(number, string) -> (string, number)';
this is because
* it returns the 1st entry in the type pack is `string` in the latter type and `number` in the former type, and `string` is not a subtype of `number`
* it returns the 2nd entry in the type pack is `number` in the latter type and `string` in the former type, and `number` is not a subtype of `string`
* it takes the 1st entry in the type pack is `number` in the latter type and `string` in the former type, and `number` is not a supertype of `string`
* it takes the 2nd entry in the type pack is `string` in the latter type and `number` in the former type, and `string` is not a supertype of `number`
A konečně si, prozatím ve stručnosti, ukažme typovou definici funkce bez parametrů i bez návratové hodnoty:
--!strict function noop() end local x: () -> () x = noop x()
Rozdíl mezi jazykem Lua a Pythonem u funkcí vracejících „nic“
Na tomto místě je vhodné upozornit na jeden zajímavý rozdíl mezi jazykem Lua a jazykem Python, který se týká funkcí vracejících „nic“. Předchozí větu jsem napsal schválně tímto způsobem, protože v jazyce Lua se (i když to nemusí být na první pohled patrné) rozlišuje mezi funkcí vracející „nic“ a funkcí vracející hodnotu nil.
Nejdříve se podívejme, jak je tomu v Pythonu. Následující trojice funkcí se v runtime chová totožným způsobem, i když jsou jejich těla odlišná:
def noop1() -> None:
pass
def noop2() -> None:
return
def noop3() -> None:
return None
x = noop1()
print(type(x), x)
x = noop2()
print(type(x), x)
x = noop3()
print(type(x), x)
Toto chování si pochopitelně můžeme snadno ověřit:
$ python none_function.py <class 'NoneType'> None <class 'NoneType'> None <class 'NoneType'> None
V programovacím jazyce Lua (resp. i Luau) se ovšem rozlišuje mezi funkcí bez příkazu return a funkcí vracející hodnotu nil. První z těchto funkcí totiž vlastně vrací nulový počet hodnot (tedy prázdnou n-tici, i když se s tímto označením v jazyku Lua neoperuje):
--!strict
function noop1()
end
function noop2()
return
end
function noop3()
return nil
end
local f: () -> ()
local x: any = "any"
f = noop1
x = f()
print(type(x), x)
f = noop2
x = f()
print(type(x), x)
f = noop3
x = f()
print(type(x), x)
V čase běhu se všechny tři funkce chovají zdánlivě prakticky stejně:
$ ./luau function_type_8.lua nil nil nil nil nil nil
Ovšem statická typová kontrola odhalí, že poslední z funkcí má odlišný typ, než typ proměnné f:
$ ./luau-analyze function_type_8.lua ./function_type_8.lua(25,5): TypeError: Expected this to be '() -> ()', but got '(...any) -> nil'; it returns `nil` in the latter type and `()` in the former type, and `nil` is not a subtype of `()`
Nepovinné parametry funkcí
U poměrně mnoha funkcí je žádoucí (a čitelnější), aby některé jejich parametry byly nepovinné (optional). I takové parametry typový systém jazyka Luau podporuje; pouze postačuje za jméno nepovinného parametru zapsat otazník. Pokud není parametr při volání funkce předán, je do něj dosazena hodnota nil.
Příkladem může být funkce add, která dokáže sečíst dvě nebo tři číselné hodnoty:
--!strict
function add(x: number, y: number, z: number?): number
if z == nil then
return x + y
else
return x + y + z
end
end
print(add(1, 2))
print(add(1, 2, 3))
print(add(1, 2, 3, 4))
Tento příklad je z pohledu jazyka Lua zcela korektní (a to i poslední volání se čtyřmi parametry):
$ ./luau optional_type_1.lua 3 6 6
Ani statická typová analýza nenalezne žádné problémy:
$ ./luau-analyze optional_type_1.lua
Pokusme se nyní do skriptu se stejnou funkcí přidat programový řádek, ve kterém tuto funkci voláme, ovšem jen s jediným parametrem:
--!strict
function add(x: number, y: number, z: number?): number
if z == nil then
return x + y
else
return x + y + z
end
end
print(add(1))
print(add(1, 2))
print(add(1, 2, 3))
print(add(1, 2, 3, 4))
První volání povede ke vzniku běhové chyby:
$ ./luau optional_type_2.lua ./optional_type_2.lua:5: attempt to perform arithmetic (add) on number and nil stacktrace: ./optional_type_2.lua:5 function add ./optional_type_2.lua:11
Pokud nechceme čekat až na pád při běhu aplikace („ideálně“ na produkčním systému), lze použít statickou typovou analýzu, která tuto chybu objeví a nahlásí:
$ ./luau-analyze optional_type_2.lua ./optional_type_2.lua(11,7): TypeError: Argument count mismatch. Function expects 2 to 3 arguments, but only 1 is specified
Od běžných funkcí k funkcím generickým
Podívejme se ještě jednou na funkci swap, která vrací své parametry, ovšem v opačném pořadí:
function swap(x:number, y:number):(number,number)
return y, x
end
V praxi by bylo žádoucí mít možnost tuto funkci rozšířit takovým způsobem, aby akceptovala a vracela hodnoty jiných typů, než jen number. První řešení by mohlo vypadat takto:
function swap(x:any, y:any):(any,any)
return y, x
end
To ovšem není nejlepší možné řešení. Problém spočívá v tom, že zápisem any se vlastně vzdáváme statické typové kontroly části programového kódu. Nedokážeme například popsat, že první návratová hodnota má stejný typ, jako druhý parametr funkce atd. Řešení spočívá v definici generické funkce, v níž se za generické typy T a U doplní konkrétní typy získané ze zdrojového kódu:
--!strict
function swap<T, U>(x: T, y: U): (U, T)
return y, x
end
local x:string
local y:number
x,y = swap(1, "foo")
print(x, y)
Tento skript je z pohledu typového systému zcela bezpečný:
$ ./luau-analyze functions_4.lua
Pokusme se do programu zanést chybu – zaměníme typy proměnných x a y:
--!strict
function swap<T, U>(x: T, y: U): (U, T)
return y, x
end
local x:number
local y:string
x,y = swap(1, "foo")
print(x, y)
Tento problém je v průběhu statické typové analýzy snadno odhalen:
$ ./luau-analyze functions_5.lua ./functions_5.lua(19,7): TypeError: Expected this to be 'number', but got 'string'
Snadno taktéž dokážeme zapsat funkci, která musí mít dva parametry stejného typu a vrací dvě hodnoty téhož typu:
--!strict
function swap<T<(x: T, y: T): (T, T)
return y, x
end
local x:string
local y:number
x,y = swap(1, "foo")
print(x, y)
Opět se podívejme na výsledek statické typové kontroly:
$ ./luau-analyze functions_6.lua ./functions_6.lua(19,7): TypeError: Expected this to be 'string', but got 'number | string'; the 1st component of the union is `number`, which is not a subtype of `string`
Definice nových datových typů
S definicí nových datových typů jsme se vlastně již v dnešním článku setkali, takže jen v krátkosti. V programovacím jazyku Luau je definováno klíčové slovo type (má tedy stejný identifikátor, jako funkce type), které umožňuje pojmenování a definici nového datového typu. Příkladem je definice typu float, jenž je odvozený od typu number:
--!strict type float = number local x: float = 3.14 print(type(x)) print(typeof(x))
Naprosto stejným postupem můžeme definovat i typ id, který vznikne sloučením dvou existujících typů:
--!strict type id = number | string local x: id = "foo" print(type(x)) print(typeof(x)) x = 42 print(type(x)) print(typeof(x))
Zprávy vypsané po spuštění by neměly být překvapivé:
$ ./luau type_declaration_2.lua string string number number
Stejně tak si můžeme vytvořit nový datový typ, který umožňuje, aby proměnná či parametr funkce neobsahoval žádnou hodnotu (resp. bude obsahovat výchozí hodnotu nil). Tak vlastně docházíme k typu Optional:
--!strict type optional_number = number | nil local x: optional_number print(x) print(type(x)) print(typeof(x)) print() x = 42 print(x) print(type(x)) print(typeof(x)) print() x = "foo" print(x) print(type(x)) print(typeof(x))
Otestování funkcionality:
$ ./luau type_declaration_3.lua nil nil nil 42 number number foo string string
V příkladu je samozřejmě chyba, kterou odhalí statická typová analýza:
$ ./luau-analyze type_declaration_3.lua ./type_declaration_3.lua(19,5): TypeError: Expected this to be 'number?', but got 'string'
Ovšem komu by se chtělo stále psát „typ | nil“ (nejsme v Pythonu). Následující demonstrační příklad je vlastně totožný s příkladem předchozím, ovšem používáme zde zápis typ? (s otazníkem na konci):
--!strict type optional_number = number? local x: optional_number print(x) print(type(x)) print(typeof(x)) print() x = 42 print(x) print(type(x)) print(typeof(x)) print() x = "foo" print(x) print(type(x)) print(typeof(x))
Výsledek statické typové analýzy tohoto příkladu:
$ ./luau-analyze type_declaration_4.lua ./type_declaration_4.lua(19,5): TypeError: Expected this to be 'number?', but got 'string'
Funkce odpovídající zapsanému typu
Definice nového typu s využitím klíčového slova type lze využít i v případě, že budeme chtít pojmenovat typ popisující určité funkce. Tento postup je ukázán v dalším demonstračním příkladu s typem popisujícím libovolnou funkci akceptující jeden parametr typu number a vracející jedinou hodnotu stejného typu:
--!strict
type math_function = (number) -> number
function zero(x: number): number
return 0
end
function inc(x: number): number
return x+1
end
function dec(x: number): number
return x-1
end
local x: math_function
x = zero
print(x(42))
x = inc
print(x(42))
x = dec
print(x(42))
Tento příklad se bude v runtime chovat korektně:
$ ./luau type_declaration_5.lua 0 43 41
Ani statická typová analýza neodhalí problém:
$ ./luau-analyze type_declaration_5.lua
Pozor si ovšem musíme dát na jednu past typového systému. Na první pohled by se mohlo zdát, že je následující demonstrační příklad korektní, protože každá z funkcí akceptuje buď parametr typu number nebo string, což by (opět – jen na první pohled) mohlo odpovídat typu (number | string) → boolean:
--!strict
type login_implementation = (number | string) -> boolean
function login_by_id(id: number): boolean
return id == 0
end
function login_by_name(name: string): boolean
return name == "root"
end
local login: login_implementation
login = login_by_id
print(login(0))
print(login(1))
login = login_by_name
print(login("root"))
print(login("admin"))
Ve skutečnosti je ovšem v programu chyba – každá z funkcí by měla akceptovat jak hodnotu typu number, tak i hodnotu typu string:
$ ./luau-analyze type_declaration_6.lua
./type_declaration_6.lua(15,9): TypeError: Expected this to be
'(number | string) -> boolean'
but got
'(number) -> boolean';
it takes the 1st entry in the type pack is `number` and it takes the 1st entry
in the type pack has the 2nd component of the union as `string`, and `number`
is not a supertype of `string`
./type_declaration_6.lua(19,9): TypeError: Expected this to be
'(number | string) -> boolean'
but got
'(string) -> boolean';
it takes the 1st entry in the type pack is `string` and it takes the 1st entry
in the type pack has the 1st component of the union as `n
Funkce spouštěné v době statické typové analýzy
Při provádění statické typové analýzy (nikoli při běhu skriptu!) je možné spouštět funkce, které začínají dvojicí klíčových slov type a function. Ovšem volání takových funkcí je nepřímé – je totiž realizováno pouze při deklaraci datového typu, specifikace typu proměnné, parametru funkce atd. Typové funkce musí vracet hodnotu určující nějaký datový typ.
Příkladem je funkce foo, která vrací typ boolean, jenž je použit při definici typu proměnné x:
type function foo()
return types.bool
end
local x:foo<> = false
print(type(x), x)
V těle této funkce můžeme testovat, jaký typ či typy jsou této funkci předány (opět – v době statické typové kontroly). Následující příklad je sice umělý, ale ukazuje sílu nástrojů jazyka Luau:
type function bar(x)
if x:is("table") then
return types.bool
else
return types.number
end
end
type Coord = {coord:number}
local x:bar<boolean> = false
print(type(x), x)
local y:bar<Coord> = {coord=42}
print(type(y), y)
A konečně – v dalším příkladu si ověříme, kdy je vlastně funkce bar volána. Přidáme do ní totiž příkaz print:
type function bar(x)
print("bar", x)
if x:is("table") then
return types.bool
else
return types.number
end
end
type Coord = {coord:number}
local x:bar<boolean> = false
print(type(x), x)
local y:bar<Coord> = {coord=42}
print(type(y), y)
Běžné spuštění tohoto příkladu nevede k volání funkce bar:
$ ./luau type_function_3.lua boolean false table table: 0x0000000025d47ea0
Ovšem při statické typové kontrole se tato funkce skutečně zavolá (i když se příkaz print vypisuje formou chyby):
$ ./luau-analyze type_function_3.lua
./type_function_3.lua(12,9): TypeError: bar boolean
./type_function_3.lua(15,9): TypeError: bar { coord: number }
Sjednocené typy se jmenovkou
Typový systém programovacího jazyka Luau podporuje i tagged unions neboli (poněkud kostrbatě přeloženo) sjednocené typy se jmenovkou. Ve skutečnosti se ovšem nejedná o specializovanou vlastnost typového systému, protože tagged unions vzniknou kombinací běžného sjednocení typů (union) s možností definice generických datových typů – a obě tyto vlastnosti již známe.
Základem pro tagged unions jsou typy odvozené od tabulek. V následujícím příkladu jsou definovány dva nové datové typy Ok a Error, dále jsou vytvořeny proměnné těchto typů a obsah proměnných je vypsán. To jsou všechno operace, které již dobře známe:
--!strict
type Ok = { type: "ok", value: string}
type Error = { type: "error", error: string}
local x: Ok = { type="ok", value="foo" }
local y: Error = { type="error", error="PC LOAD LETTER" }
print(type(x), x.type, x.value)
print(type(y), y.type, y.error)
$ ./luau tagged_union_1.lua table ok foo table error PC LOAD LETTER
Nic nám samozřejmě nebrání v deklaraci typu Result, který vznikne sjednocením typů Ok a Error. Můžeme pracovat i s proměnnou typu Result (i když tisk hodnoty a chyby není korektní – vždy jeden z těchto prvků nebude existovat):
--!strict
type Ok = { type: "ok", value: string}
type Error = { type: "error", error: string}
type Result = Ok | Error
local x: Ok = { type="ok", value="foo" }
local y: Error = { type="error", error="PC LOAD LETTER" }
local z: Result = x
print(type(z), z.type, z.value, z.error)
z = y
print(type(z), z.type, z.value, z.error)
Chování tohoto skriptu:
$ ./luau tagged_union_2.lua table ok foo nil table error nil PC LOAD LETTER
Tento skript pochopitelně neprojde statickou typovou kontrolou, protože se snažíme o výpis obou hodnot value i error. To je ovšem v praxi snadno řešitelné testem hodnoty uložené v prvku type:
$ ./luau-analyze tagged_union_2.lua ./tagged_union_2.lua(11,24): TypeError: Key 'value' is missing from 'Error' in the type 'Error | Ok' ./tagged_union_2.lua(11,33): TypeError: Key 'error' is missing from 'Ok' in the type 'Error | Ok' ./tagged_union_2.lua(14,24): TypeError: Key 'value' not found in table 'Error'
K plnohodnotnému typu tagged union nám zbývá pouze jediný krok. Všechny tři typy Ok, Error i Result totiž musí být generické, protože dopředu nevíme, jakého typu budou uložené hodnoty a vlastně ani chyba. Řešení vypadá následovně:
--!strict
type Ok<T> = { type: "ok", value: T}
type Error<E> = { type: "error", error: E}
type Result<T, E> = Ok<T> | Error<E>
Závěr
Jak již bylo napsáno v teoretické části úvodního článku, jsou statické typové kontroly v současnosti nepovinnou, ale mnohdy o to důležitější součástí i těch programovacích jazyků, které původně nabízely pouze dynamický typový systém. Díky tomuto rozšíření sémantiky i syntaxe jazyků je možné psát i rozsáhlejší aplikace, přičemž typové informace dokážou nahradit některé typy testů, jsou součástí přirozené dokumentace zdrojového kódu a díky přidanému kontextu pomůžou i při vývoji s využitím LLM a agentů. V ekosystému programovacího jazyka Lua tuto funkci zastává Luau. Typový systém, který je v tomto projektu implementován, je v určitých ohledech podobný typovému systému Pythonu, ale není zcela totožný: odlišnosti nalezneme při práci s tabulkami, ale taktéž s hodnotou a typem nil.
Repositář s demonstračními příklady
Všechny demonstrační příklady popsané v úvodním i v dnešním článku naleznete i v repositáři https://github.com/tisnik/luau-examples. Následují odkazy na jednotlivé příklady. Pro jejich otestování je zapotřebí použít interpret luau a nástroj pro statickou analýzu luau-analyze:
| # | Příklad | Stručný popis | Adresa příkladu |
|---|---|---|---|
| 1 | runtime_types.lua | tisk typu hodnoty, který je získaný v čase běhu programu (runtime) | https://github.com/tisnik/luau-examples/blob/master/runtime_types.lua |
| 2 | simple_buffer.lua | základní způsob použití bufferu, tj. hodnoty typu buffer | https://github.com/tisnik/luau-examples/blob/master/simple_buffer.lua |
| 3 | type_annotations_1.lua | typové anotace použité při deklaraci proměnných | https://github.com/tisnik/luau-examples/blob/master/type_annotations_1.lua |
| 4 | type_annotations_2.lua | pokus o provedení operace pro nekompatibilní datové typy | https://github.com/tisnik/luau-examples/blob/master/type_annotations_2.lua |
| 5 | type_annotations_3.lua | pokus o inicializaci proměnné hodnotou nekompatibilního typu | https://github.com/tisnik/luau-examples/blob/master/type_annotations_3.lua |
| 6 | any_type_1.lua | typ any | https://github.com/tisnik/luau-examples/blob/master/any_type_1.lua |
| 7 | any_type_2.lua | důkaz, že statická typová kontrola nemusí odhalit všechny problematické operace | https://github.com/tisnik/luau-examples/blob/master/any_type_2.lua |
| 8 | any_type_3.lua | běhové informace o typu vs. statický typ any | https://github.com/tisnik/luau-examples/blob/master/any_type_3.lua |
| 9 | foobar_nocheck.lua | zákaz typových kontrol s využitím strukturovaného komentáře | https://github.com/tisnik/luau-examples/blob/master/foobar_nocheck.lua |
| 10 | foobar_nonstrict.lua | zákaz striktních typových kontrol s využitím strukturovaného komentáře | https://github.com/tisnik/luau-examples/blob/master/foobar_nonstrict.lua |
| 11 | foobar_strict.lua | povolení striktních typových kontrol s využitím strukturovaného komentáře | https://github.com/tisnik/luau-examples/blob/master/foobar_strict.lua |
| 12 | foobar_types.lua | zjištění skutečných typů hodnot v čase běhu programu (runtime) | https://github.com/tisnik/luau-examples/blob/master/foobar_types.lua |
| 13 | functions_1.lua | typové anotace parametrů funkce i její návratové hodnoty | https://github.com/tisnik/luau-examples/blob/master/functions_1.lua |
| 14 | functions_2.lua | typové anotace funkce vracející dvojici hodnot | https://github.com/tisnik/luau-examples/blob/master/functions_2.lua |
| 15 | functions_3.lua | využití typu any při zápisu typové anotace funkce | https://github.com/tisnik/luau-examples/blob/master/functions_3.lua |
| 16 | functions_4.lua | generická funkce: základní způsob zápisu | https://github.com/tisnik/luau-examples/blob/master/functions_4.lua |
| 17 | functions_5.lua | generická funkce: základní způsob zápisu, odlišné skutečné parametry při volání | https://github.com/tisnik/luau-examples/blob/master/functions_5.lua |
| 18 | functions_6.lua | generické funkce a statické typové kontroly | https://github.com/tisnik/luau-examples/blob/master/functions_6.lua |
| 19 | table_type_1.lua | typová anotace tabulky použité jako běžné pole | https://github.com/tisnik/luau-examples/blob/master/table_type_1.lua |
| 20 | table_type_2.lua | předání nekorektního typu tabulky | https://github.com/tisnik/luau-examples/blob/master/table_type_2.lua |
| 21 | table_type_3.lua | tabulka použitá v roli slovníku | https://github.com/tisnik/luau-examples/blob/master/table_type_3.lua |
| 22 | table_type_4.lua | typová anotace tabulky použité v roli slovníku (korektní varianta) | https://github.com/tisnik/luau-examples/blob/master/table_type_4.lua |
| 23 | table_type_5.lua | typová anotace tabulky použité v roli slovníku (nekorektní varianta) | https://github.com/tisnik/luau-examples/blob/master/table_type_5.lua |
| 24 | table_type_6.lua | typová anotace nepovinného prvku tabulky | https://github.com/tisnik/luau-examples/blob/master/table_type_6.lua |
| 25 | table_type_7.lua | definice nového datového typu založeného na tabulce | https://github.com/tisnik/luau-examples/blob/master/table_type_7.lua |
| 26 | table_type_8.lua | typová kompatibilita různých typů založených na tabulkách | https://github.com/tisnik/luau-examples/blob/master/table_type_8.lua |
| 27 | table_type_9.lua | typová kompatibilita různých typů založených na tabulkách | https://github.com/tisnik/luau-examples/blob/master/table_type_9.lua |
| 28 | typeof.lua | získání informace o typu s využitím vestavěné funkce typeof | https://github.com/tisnik/luau-examples/blob/master/typeof.lua |
| 29 | union_type_1.lua | definice proměnné s uvedením konkrétního datového typu | https://github.com/tisnik/luau-examples/blob/master/union_type_1.lua |
| 30 | union_type_2.lua | definice proměnné s uvedením sjednocení (union) datových typů | https://github.com/tisnik/luau-examples/blob/master/union_type_2.lua |
| 31 | union_type_3.lua | sjednocení datových typů použité při definici funkce | https://github.com/tisnik/luau-examples/blob/master/union_type_3.lua |
| 32 | union_type_4.lua | sjednocení datových typů použité při definici funkce, práce s návratovými hodnotami | https://github.com/tisnik/luau-examples/blob/master/union_type_4.lua |
| 33 | union_type_5.lua | z pohledu typového systému nekorektní práce s návratovými hodnotami | https://github.com/tisnik/luau-examples/blob/master/union_type_5.lua |
| 34 | union_type_6.lua | korektní práce s návratovými hodnotami | https://github.com/tisnik/luau-examples/blob/master/union_type_6.lua |
| 35 | intersection_type_1.lua | definice dvouprvkového vektoru průnikem dvou datových typů (intersection) | https://github.com/tisnik/luau-examples/blob/master/intersection_type_1.lua |
| 36 | intersection_type_2.lua | definice dvouprvkového i tříprvkového vektoru průnikem dvou typů (intersection) | https://github.com/tisnik/luau-examples/blob/master/intersection_type_2.lua |
| 37 | intersection_type_3.lua | příklad proměnné nekorektního typu | https://github.com/tisnik/luau-examples/blob/master/intersection_type_3.lua |
| 38 | intersection_type_4.lua | odlišný způsob realizace průniku dvou datových typů (korektní příklad) | https://github.com/tisnik/luau-examples/blob/master/intersection_type_4.lua |
| 39 | intersection_type_5.lua | odlišný způsob realizace průniku dvou datových typů (nekorektní příklad) | https://github.com/tisnik/luau-examples/blob/master/intersection_type_5.lua |
| 40 | function_type_1.lua | typ funkce s jedním parametrem a jednou návratovou hodnotou | https://github.com/tisnik/luau-examples/blob/master/function_type_1.lua |
| 41 | function_type_2.lua | příklady přiřazení funkce s nekorektním typem | https://github.com/tisnik/luau-examples/blob/master/function_type_2.lua |
| 42 | function_type_3.lua | typ funkce s jedním parametrem, ovšem bez návratové hodnoty | https://github.com/tisnik/luau-examples/blob/master/function_type_3.lua |
| 43 | function_type_4.lua | použití nekompatibilního přiřazení | https://github.com/tisnik/luau-examples/blob/master/function_type_4.lua |
| 44 | function_type_5.lua | typ funkce se dvěma parametry a dvojicí návratových hodnot | https://github.com/tisnik/luau-examples/blob/master/function_type_5.lua |
| 45 | function_type_6.lua | použití nekompatibilního přiřazení | https://github.com/tisnik/luau-examples/blob/master/function_type_6.lua |
| 46 | function_type_7.lua | typ funkce bez parametrů a bez návratových hodnot | https://github.com/tisnik/luau-examples/blob/master/function_type_7.lua |
| 47 | function_type_8.lua | funkce bez návratové hodnoty vs. funkce vracející nil | https://github.com/tisnik/luau-examples/blob/master/function_type_8.lua |
| 48 | type_declaration_1.lua | deklarace nového typu odvozeného od typu existujícího | https://github.com/tisnik/luau-examples/blob/master/type_declaration_1.lua |
| 49 | type_declaration_2.lua | deklarace nového typu získaného sjednocením existujících typů | https://github.com/tisnik/luau-examples/blob/master/type_declaration_2.lua |
| 50 | type_declaration_3.lua | deklarace nového typu s nepovinnou hodnotou (optional) | https://github.com/tisnik/luau-examples/blob/master/type_declaration_3.lua |
| 51 | type_declaration_4.lua | deklarace nového typu s nepovinnou hodnotou (optional) | https://github.com/tisnik/luau-examples/blob/master/type_declaration_4.lua |
| 52 | type_declaration_5.lua | deklarace typu funkce (korektní příklad deklarace) | https://github.com/tisnik/luau-examples/blob/master/type_declaration_5.lua |
| 53 | type_declaration_6.lua | deklarace typu funkce (nekorektní příklad deklarace) | https://github.com/tisnik/luau-examples/blob/master/type_declaration_6.lua |
| 54 | optional_type_1.lua | funkce s nepovinným parametrem, korektní příklad | https://github.com/tisnik/luau-examples/blob/master/optional_type_1.lua |
| 55 | optional_type_2.lua | funkce s nepovinným parametrem, nekorektní příklad | https://github.com/tisnik/luau-examples/blob/master/optional_type_2.lua |
| 56 | tagged_union_1.lua | definice typů Ok a Error | https://github.com/tisnik/luau-examples/blob/master/tagged_union_1.lua |
| 57 | tagged_union_2.lua | definice typů Ok, Error a Result | https://github.com/tisnik/luau-examples/blob/master/tagged_union_2.lua |
| 58 | tagged_union_3.lua | definice generického typu Ok, Error a Result | https://github.com/tisnik/luau-examples/blob/master/tagged_union_3.lua |
| 59 | type_function_1.lua | funkce spouštěné v průběhu statické typové analýzy | https://github.com/tisnik/luau-examples/blob/master/type_function_1.lua |
| 60 | type_function_2.lua | funkce spouštěné v průběhu statické typové analýzy | https://github.com/tisnik/luau-examples/blob/master/type_function_2.lua |
| 61 | type_function_3.lua | funkce spouštěné v průběhu statické typové analýzy | https://github.com/tisnik/luau-examples/blob/master/type_function_3.lua |
Odkazy na Internetu
- Repositář projektu luau
https://github.com/luau-lang/luau - Oficiální vydání
https://github.com/luau-lang/luau - An introduction to Luau
https://luau.org/getting-started/ - Luau Programming Full Course (2026) – For Beginners
https://www.youtube.com/watch?v=9kdRHP-FZCA - Roblox: Luau
https://create.roblox.com/docs/luau - How to start learning luau?
https://devforum.roblox.com/t/how-to-start-learning-luau/3823179 - Luau (programming language)
https://en.wikipedia.org/wiki/Luau_(programming_language) - Gradual typing
https://en.wikipedia.org/wiki/Gradual_typing - Gradual typing in Python
https://www.geeksforgeeks.org/python/gradual-typing-in-python/ - Łukasz Langa: Gradual Typing of Production Applications
https://www.youtube.com/watch?v=zKre4DKAB30 - lupa na GitHubu
https://github.com/scoder/lupa - lupa na PyPi
https://pypi.org/project/lupa/ - Lunatic Python
https://labix.org/lunatic-python - Seriál Programovací jazyk Lua
https://www.root.cz/serialy/programovaci-jazyk-lua/ - Python na Rootu
https://www.root.cz/n/python/ - Lua na Wikipedii
https://en.wikipedia.org/wiki/Lua - Python (programming language) na Wikipedii
https://en.wikipedia.org/wiki/Python_(programming_language) - 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 - 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 - 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 - ULua: Universal Lua Distribution
https://ulua.io/index.html - deque
https://github.com/catwell/cw-lua/blob/master/deque/deque.test.lua - Repositář projektu Lua Fun
https://github.com/luafun/luafun - Lua Functional 0.1.3 documentation
https://luafun.github.io/reference.html - Lua Functional: Getting Started
https://luafun.github.io/getting_started.html - LuaRocks
https://luarocks.org/ - Documentation
Dokumentace ke správci balíčků LuaRocks - Rockspec knihovny Fun
https://raw.githubusercontent.com/luafun/luafun/master/fun-scm-1.rockspec - 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 - Repositář projektu Moses
https://github.com/Yonaba/Moses/ - Návod k použití knihovny Moses
https://github.com/Yonaba/Moses/blob/master/doc/tutorial.md - 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 - Programming in Lua 9.1 – Coroutine Basics
http://www.lua.org/pil/9.1.html - How to understand clojure's lazy-seq
https://stackoverflow.com/questions/44095400/how-to-understand-clojures-lazy-seq - Lua Implementations
http://lua-users.org/wiki/LuaImplementations - Generator (computer programming)
https://en.wikipedia.org/wiki/Generator_(computer_programming) - Lua 5.2 Reference Manual: 6.10 – The Debug Library
http://www.lua.org/manual/5.2/manual.html#6.10 - Issues k projektu LuaFun
https://github.com/luafun/luafun/issues - Archived | Embed Lua for scriptable apps
https://developer.ibm.com/tutorials/l-embed-lua/ - Embedding Lua
https://www.oreilly.com/library/view/lua-quick-start/9781789343229/3a6f3daf-f74c-4a25-a125–584da58568e4.xhtml - PDM: moderní správce balíčků a virtuálních prostředí Pythonu
https://www.root.cz/clanky/pdm-moderni-spravce-balicku-a-virtualnich-prostredi-pythonu/ - Which Programming Languages Index by One?
https://therenegadecoder.com/code/which-programming-languages-index-by-one/ - 2Zero-based indexing considered harmful
https://craftofcoding.wordpress.com/2021/03/26/zero-based-indexing-considered-harmful/ - Lua downloads
https://www.lua.org/download.html - An introduction to Luau types
https://luau.org/types/ - PEP 585 – Type Hinting Generics In Standard Collections
https://peps.python.org/pep-0585/ - mypy homepage
https://www.mypy-lang.org/ - mypy documentation
https://mypy.readthedocs.io/en/stable/ - Mypy na PyPi Optional static typing for Python
https://pypi.org/project/mypy/ - 5 Reasons Why You Should Use Type Hints In Python
https://www.youtube.com/watch?v=dgBCEB2jVU0 - Python Typing – Type Hints & Annotations
https://www.youtube.com/watch?v=QORvB-_mbZ0 - What Problems Can TypeScript Solve?
https://www.typescriptlang.org/why-create-typescript - Type system (Wikipedia)
https://en.wikipedia.org/wiki/Type_system - PYPL PopularitY of Programming Language
https://pypl.github.io/PYPL.html - How Different Programming Languages Handle Data Types
https://learncodingusa.com/programming-languages-data-types/ - Exploring Data Types in Different Coding Languages
https://learncodingusa.com/data-types-in-coding-languages/
