Luau: rozšíření jazyka Lua o statické typové kontroly a typovou inferenci (dokončení)

Dnes
Doba čtení: 37 minut

Sdílet

Autor: Depositphotos
Na úvodní článek o typovém systému v projektu Luau dnes navážeme a celé téma dokončíme. Vysvětlíme si podrobnosti o typovém systému, včetně hierarchie datových typů, generických funkcích a funkcích spouštěných při typové analýze.

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
  1. Funkce type a typeof
  2. Hierarchie datových typů
  3. Sjednocení typů
  4. Problematický případ a jeho řešení
  5. Průnik typů
  6. Příklady použití průniku typů
  7. Specifikace typu „funkce“
  8. Praktické příklady specifikací typu „funkce“ u proměnných
  9. Komplikovanější příklady specifikace typu „funkce“
  10. Rozdíl mezi jazykem Lua a Pythonem u funkcí vracejících „nic“
  11. Nepovinné parametry funkcí
  12. Od běžných funkcí k funkcím generickým
  13. Definice nových datových typů
  14. Funkce odpovídající zapsanému typu
  15. Funkce spouštěné v době statické typové analýzy
  16. Sjednocené typy se jmenovkou
  17. Závěr
  18. Repositář s demonstračními příklady
  19. 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.

Logo projektu Luau

 Obrázek 1: Logo programovacího jazyka Luau už příliš nepřipomíná Měsíc obíhající okolo Země.

Autor: Roblox, podle licence: Rights Managed

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
Poznámka: nutno dodat, že typeof je mnohem lepší pojmenování, protože nekoliduje s novým klíčovým slovem type, k němuž se dnes ještě vrátíme.

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
Poznámka: v Luau je použit spíše strukturální typový systém, nikoli systém nominální. Velká část typového systému (zejména průniky) je založena na vlastnostech tabulek, což si taktéž vysvětlíme v navazujících kapitolách.

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 `()`
Poznámka: v praxi je pouze vhodné o této vlastnosti jazyka Lua vědět, aby bylo možné pochopit hlášení, která vypisuje program pro statickou typovou analýzu.

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.

Poznámka: typ s otazníkem je vlastně jen syntaktickým cukrem pro zápis typ | nil; jedná se tedy o sjednocení nějakého typu a typu nil s jedinou hodnotou, která je taktéž 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
Poznámka: tento problém je možné vyřešit generickými funkcemi.

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 }
Poznámka: to je poměrně mocný nástroj, který do určité míry nahrazuje neexistenci skutečných maker.

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 valueerror. 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, ErrorResult 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ě:

Školení Zabbix

--!strict
 
type Ok<T> = { type: "ok", value: T}
type Error<E> = { type: "error", error: E}
type Result<T, E> = Ok<T> | Error<E>
Poznámka: z posledního demonstračního příkladu možná vyplývá, že typový systém programovacího jazyka Luau ve skutečnosti vůbec není primitivní a ani se nejedná o ad-hoc řešení.

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_an­notations_1.lua
4 type_annotations_2.lua pokus o provedení operace pro nekompatibilní datové typy https://github.com/tisnik/luau-examples/blob/master/type_an­notations_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_an­notations_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_nochec­k.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_non­strict.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/inter­section_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/inter­section_type_2.lua
37 intersection_type_3.lua příklad proměnné nekorektního typu https://github.com/tisnik/luau-examples/blob/master/inter­section_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/inter­section_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/inter­section_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/functi­on_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/functi­on_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/functi­on_type_3.lua
43 function_type_4.lua použití nekompatibilního přiřazení https://github.com/tisnik/luau-examples/blob/master/functi­on_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/functi­on_type_5.lua
45 function_type_6.lua použití nekompatibilního přiřazení https://github.com/tisnik/luau-examples/blob/master/functi­on_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/functi­on_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/functi­on_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_de­claration_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_de­claration_2.lua
50 type_declaration_3.lua deklarace nového typu s nepovinnou hodnotou (optional) https://github.com/tisnik/luau-examples/blob/master/type_de­claration_3.lua
51 type_declaration_4.lua deklarace nového typu s nepovinnou hodnotou (optional) https://github.com/tisnik/luau-examples/blob/master/type_de­claration_4.lua
52 type_declaration_5.lua deklarace typu funkce (korektní příklad deklarace) https://github.com/tisnik/luau-examples/blob/master/type_de­claration_5.lua
53 type_declaration_6.lua deklarace typu funkce (nekorektní příklad deklarace) https://github.com/tisnik/luau-examples/blob/master/type_de­claration_6.lua
       
54 optional_type_1.lua funkce s nepovinným parametrem, korektní příklad https://github.com/tisnik/luau-examples/blob/master/opti­onal_type_1.lua
55 optional_type_2.lua funkce s nepovinným parametrem, nekorektní příklad https://github.com/tisnik/luau-examples/blob/master/opti­onal_type_2.lua
       
56 tagged_union_1.lua definice typů Ok a Error https://github.com/tisnik/luau-examples/blob/master/tagged_u­nion_1.lua
57 tagged_union_2.lua definice typů Ok, Error a Result https://github.com/tisnik/luau-examples/blob/master/tagged_u­nion_2.lua
58 tagged_union_3.lua definice generického typu Ok, Error a Result https://github.com/tisnik/luau-examples/blob/master/tagged_u­nion_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_fun­ction_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_fun­ction_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_fun­ction_3.lua

Odkazy na Internetu

  1. Repositář projektu luau
    https://github.com/luau-lang/luau
  2. Oficiální vydání
    https://github.com/luau-lang/luau
  3. An introduction to Luau
    https://luau.org/getting-started/
  4. Luau Programming Full Course (2026) – For Beginners
    https://www.youtube.com/watch?v=9kdRHP-FZCA
  5. Roblox: Luau
    https://create.roblox.com/docs/luau
  6. How to start learning luau?
    https://devforum.roblox.com/t/how-to-start-learning-luau/3823179
  7. Luau (programming language)
    https://en.wikipedia.org/wi­ki/Luau_(programming_langu­age)
  8. Gradual typing
    https://en.wikipedia.org/wi­ki/Gradual_typing
  9. Gradual typing in Python
    https://www.geeksforgeeks­.org/python/gradual-typing-in-python/
  10. Łukasz Langa: Gradual Typing of Production Applications
    https://www.youtube.com/wat­ch?v=zKre4DKAB30
  11. lupa na GitHubu
    https://github.com/scoder/lupa
  12. lupa na PyPi
    https://pypi.org/project/lupa/
  13. Lunatic Python
    https://labix.org/lunatic-python
  14. Seriál Programovací jazyk Lua
    https://www.root.cz/seria­ly/programovaci-jazyk-lua/
  15. Python na Rootu
    https://www.root.cz/n/python/
  16. Lua na Wikipedii
    https://en.wikipedia.org/wiki/Lua
  17. Python (programming language) na Wikipedii
    https://en.wikipedia.org/wi­ki/Python_(programming_lan­guage)
  18. Languages that compile to Lua
    https://github.com/hengestone/lua-languages?tab=readme-ov-file#languages-that-compile-to-lua
  19. Repositář projektu Lua Fun
    https://github.com/luafun/luafun
  20. The Lua VM, on the Web
    https://kripken.github.io/lu­a.vm.js/lua.vm.js.html
  21. Lua.vm.js REPL
    https://kripken.github.io/lu­a.vm.js/repl.html
  22. lua2js
    https://www.npmjs.com/package/lua2js
  23. lua2js na GitHubu
    https://github.com/basicer/lua2js-dist
  24. Lambda the Ultimate: Coroutines in Lua,
    http://lambda-the-ultimate.org/node/438
  25. Coroutines Tutorial,
    http://lua-users.org/wiki/CoroutinesTutorial
  26. Lua Coroutines Versus Python Generators,
    http://lua-users.org/wiki/LuaCorouti­nesVersusPythonGenerators
  27. ULua: Universal Lua Distribution
    https://ulua.io/index.html
  28. deque
    https://github.com/catwell/cw-lua/blob/master/deque/deque.test.lua
  29. Repositář projektu Lua Fun
    https://github.com/luafun/luafun
  30. Lua Functional 0.1.3 documentation
    https://luafun.github.io/re­ference.html
  31. Lua Functional: Getting Started
    https://luafun.github.io/get­ting_started.html
  32. LuaRocks
    https://luarocks.org/
  33. Documentation
    Dokumentace ke správci balíčků LuaRocks
  34. Rockspec knihovny Fun
    https://raw.githubusercon­tent.com/luafun/luafun/mas­ter/fun-scm-1.rockspec
  35. Awesome Lua – A curated list of quality Lua packages and resources.
    https://github.com/LewisJEllis/awesome-lua
  36. LuaJIT
    https://luajit.org/
  37. Running LuaJIT
    https://luajit.org/running.html
  38. LuaJIT na GitHubu
    https://github.com/luajit
  39. Repositář projektu Moses
    https://github.com/Yonaba/Moses/
  40. Návod k použití knihovny Moses
    https://github.com/Yonaba/Mo­ses/blob/master/doc/tutori­al.md
  41. Lambda the Ultimate: Coroutines in Lua
    http://lambda-the-ultimate.org/node/438
  42. Coroutines Tutorial
    http://lua-users.org/wiki/CoroutinesTutorial
  43. Lua Coroutines Versus Python Generators
    http://lua-users.org/wiki/LuaCorouti­nesVersusPythonGenerators
  44. Programming in Lua 9.1 – Coroutine Basics
    http://www.lua.org/pil/9.1.html
  45. How to understand clojure's lazy-seq
    https://stackoverflow.com/qu­estions/44095400/how-to-understand-clojures-lazy-seq
  46. Lua Implementations
    http://lua-users.org/wiki/LuaImplementations
  47. Generator (computer programming)
    https://en.wikipedia.org/wi­ki/Generator_(computer_pro­gramming)
  48. Lua 5.2 Reference Manual: 6.10 – The Debug Library
    http://www.lua.org/manual/5­.2/manual.html#6.10
  49. Issues k projektu LuaFun
    https://github.com/luafun/lu­afun/issues
  50. Archived | Embed Lua for scriptable apps
    https://developer.ibm.com/tutorials/l-embed-lua/
  51. Embedding Lua
    https://www.oreilly.com/li­brary/view/lua-quick-start/9781789343229/3a6f3daf-f74c-4a25-a125–584da58568e4.xhtml
  52. 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/
  53. Which Programming Languages Index by One?
    https://therenegadecoder.com/co­de/which-programming-languages-index-by-one/
  54. 2Zero-based indexing considered harmful
    https://craftofcoding.wor­dpress.com/2021/03/26/zero-based-indexing-considered-harmful/
  55. Lua downloads
    https://www.lua.org/download.html
  56. An introduction to Luau types
    https://luau.org/types/
  57. PEP 585 – Type Hinting Generics In Standard Collections
    https://peps.python.org/pep-0585/
  58. mypy homepage
    https://www.mypy-lang.org/
  59. mypy documentation
    https://mypy.readthedocs.i­o/en/stable/
  60. Mypy na PyPi Optional static typing for Python
    https://pypi.org/project/mypy/
  61. 5 Reasons Why You Should Use Type Hints In Python
    https://www.youtube.com/wat­ch?v=dgBCEB2jVU0
  62. Python Typing – Type Hints & Annotations
    https://www.youtube.com/watch?v=QORvB-_mbZ0
  63. What Problems Can TypeScript Solve?
    https://www.typescriptlang.org/why-create-typescript
  64. Type system (Wikipedia)
    https://en.wikipedia.org/wi­ki/Type_system
  65. PYPL PopularitY of Programming Language
    https://pypl.github.io/PYPL.html
  66. How Different Programming Languages Handle Data Types
    https://learncodingusa.com/programming-languages-data-types/
  67. Exploring Data Types in Different Coding Languages
    https://learncodingusa.com/data-types-in-coding-languages/

Autor článku

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