Система типов
Система типов
Заголовок раздела «Система типов»Экспериментально. Возможны некоторые ограничения.
Wippy включает постепенную систему типов с потоково-чувствительной проверкой. Типы по умолчанию non-nullable.
Примитивы
Заголовок раздела «Примитивы»local n: number = 3.14local i: integer = 42 -- integer — подтип numberlocal s: string = "hello"local b: boolean = truelocal a: any = "anything" -- явная динамика (отказ от проверки)local u: unknown = something -- нужно сузить перед использованиемany vs unknown
Заголовок раздела «any vs unknown»-- any: отказ от проверки типовlocal a: any = get_data()a.foo.bar.baz() -- нет ошибки, может упасть в runtime
-- unknown: безопасный неизвестный тип, нужно сузить перед использованиемlocal u: unknown = get_data()u.foo -- ОШИБКА: нельзя обращаться к полю unknownif type(u) == "table" then -- u сужено до table здесьendБезопасность nil
Заголовок раздела «Безопасность nil»Типы по умолчанию non-nullable. Используйте ? для опциональных значений:
local x: number = nil -- ОШИБКА: nil не присваивается numberlocal y: number? = nil -- OK: number? означает "number или nil"local z: number? = 42 -- OKСужение по потоку управления
Заголовок раздела «Сужение по потоку управления»Проверщик типов отслеживает поток управления:
local function process(x: number?): number if x ~= nil then return x -- x — number здесь end return 0end
-- Паттерн раннего возвратаlocal user, err = get_user(123)if err then return nil, err end-- user сужено до non-nil здесь
-- Или значение по умолчаниюlocal val = get_value() or 0 -- val: numberUnion-типы
Заголовок раздела «Union-типы»local val: number | string = get_value()
if type(val) == "number" then print(val + 1) -- val: numberelse print(val:upper()) -- val: stringendЛитеральные типы
Заголовок раздела «Литеральные типы»type Status = "pending" | "active" | "done"
local s: Status = "pending" -- OKlocal s: Status = "invalid" -- ОШИБКАТипы функций
Заголовок раздела «Типы функций»local function add(a: number, b: number): number return a + bend
-- Несколько возвратовlocal function div_mod(a: number, b: number): (number, number) return math.floor(a / b), a % bend
-- Возврат ошибки (идиома Lua)local function fetch(url: string): (string?, error?) -- возвращает (data, nil) или (nil, error)end
-- Функциональные типы первого классаlocal double: (number) -> number = function(x: number): number return x * 2endVariadic-функции
Заголовок раздела «Variadic-функции»local function sum(...: number): number local total: number = 0 for _, v in ipairs({...}) do total = total + v end return totalendRecord-типы
Заголовок раздела «Record-типы»type User = {name: string, age: number}
local u: User = {name = "alice", age = 25}Опциональные поля
Заголовок раздела «Опциональные поля»type Config = { host: string, port: number, timeout?: number, debug?: boolean}
local cfg: Config = {host = "localhost", port = 8080} -- OKGenerics
Заголовок раздела «Generics»local function identity<T>(x: T): T return xend
local n: number = identity(42)local s: string = identity("hello")Generics с ограничениями
Заголовок раздела «Generics с ограничениями»type HasName = {name: string}
local function greet<T: HasName>(obj: T): string return "Hello, " .. obj.nameend
greet({name = "Alice"}) -- OKgreet({age = 30}) -- ОШИБКА: отсутствует 'name'Intersection-типы
Заголовок раздела «Intersection-типы»Объединяют несколько типов:
type Named = {name: string}type Aged = {age: number}type Person = Named & Aged
local p: Person = {name = "Alice", age = 30}Tagged-юнионы
Заголовок раздела «Tagged-юнионы»type Result<T, E> = | {ok: true, value: T} | {ok: false, error: E}
type LoadState = | {status: "loading"} | {status: "loaded", data: User} | {status: "error", message: string}
local function render(state: LoadState): string if state.status == "loading" then return "Loading..." elseif state.status == "loaded" then return "Hello, " .. state.data.name elseif state.status == "error" then return "Error: " .. state.message endendТип never
Заголовок раздела «Тип never»never — нижний тип, не имеет значений:
function fail(msg: string): never error(msg)endПаттерн обработки ошибок
Заголовок раздела «Паттерн обработки ошибок»Проверщик понимает идиому ошибок Lua:
local value, err = call()if err then -- value — nil здесь return nil, errend-- value — non-nil здесь, err — nilprint(value)Утверждение non-nil
Заголовок раздела «Утверждение non-nil»Используйте ! для утверждения, что выражение не nil:
local user: User? = get_user()local name = user!.name -- утверждаем, что user не nilЕсли значение во время выполнения nil, возникает ошибка. Используйте, когда вы знаете, что значение не может быть nil, но проверщик типов не может это доказать.
Приведения типов
Заголовок раздела «Приведения типов»Безопасное приведение (валидация)
Заголовок раздела «Безопасное приведение (валидация)»Вызовите тип как функцию для валидации и приведения:
local data: any = get_json()local user = User(data) -- валидирует и возвращает Userlocal name = user.name -- безопасный доступ к полюРаботает с примитивами и пользовательскими типами:
local x: any = get_value()local s = string(x) -- приведение к stringlocal n = integer(x) -- приведение к integerlocal b = boolean(x) -- приведение к boolean
type Point = {x: number, y: number}local p = Point(data) -- валидирует структуру recordМетод Type:is()
Заголовок раздела «Метод Type:is()»Валидирует без выброса ошибки, возвращает (value, nil) или (nil, error):
type Point = {x: number, y: number}local data: any = get_input()
local p, err = Point:is(data)if p then local sum = p.x + p.y -- p — валидный Pointelse return nil, err -- валидация не прошлаendРезультат сужается в условиях:
if Point:is(data) then local p: Point = data -- data сужено до PointendНебезопасное приведение
Заголовок раздела «Небезопасное приведение»Используйте :: или as для непроверяемых приведений:
local data: any = get_data()local user = data :: User -- без проверки в runtimelocal user = data as User -- то же что и ::Используйте редко. Небезопасные приведения обходят валидацию и могут вызвать ошибки в runtime, если значение не соответствует типу.
Рефлексия типов
Заголовок раздела «Рефлексия типов»Типы — значения первого класса с методами интроспекции.
Kind и Name
Заголовок раздела «Kind и Name»print(Number:kind()) -- "number"print(Point:kind()) -- "record"print(Point:name()) -- "Point"Поля record
Заголовок раздела «Поля record»Итерация по полям record:
type User = {name: string, age: number}
for name, typ in User:fields() do print(name, typ:kind())end-- name string-- age numberДоступ к типам отдельных полей:
local nameType = User.name -- тип поля 'name'print(nameType:kind()) -- "string"Типы коллекций
Заголовок раздела «Типы коллекций»local arr: {number} = {1, 2, 3}local arrType = typeof(arr)print(arrType:elem():kind()) -- "number"
local map: {[string]: number} = {}local mapType = typeof(map)print(mapType:key():kind()) -- "string"print(mapType:val():kind()) -- "number"Опциональные типы
Заголовок раздела «Опциональные типы»local opt: number? = nillocal optType = typeof(opt)print(optType:kind()) -- "optional"print(optType:inner():kind()) -- "number"Union-типы
Заголовок раздела «Union-типы»type Status = "pending" | "active" | "done"
for variant in Status:variants() do print(variant)endТипы функций
Заголовок раздела «Типы функций»local fn: (number, string) -> boolean
local fnType = typeof(fn)for param in fnType:params() do print(param:kind())endprint(fnType:ret():kind()) -- "boolean"Сравнение типов
Заголовок раздела «Сравнение типов»print(Number == Number) -- trueprint(Integer <= Number) -- true (подтип)print(Integer < Number) -- true (строгий подтип)Типы как ключи таблиц
Заголовок раздела «Типы как ключи таблиц»local handlers = {}handlers[Number] = function() return "number handler" endhandlers[String] = function() return "string handler" end
local h = handlers[typeof(value)]if h then h() endАннотации типов
Заголовок раздела «Аннотации типов»Добавляйте типы в сигнатуры функций:
-- Типы параметров и возвратаlocal function process(input: string): number return #inputend
-- Типы локальных переменныхlocal count: number = 0
-- Псевдонимы типовtype StringArray = {string}type StringMap = {[string]: number}Валидаторы типов
Заголовок раздела «Валидаторы типов»Добавляйте runtime-ограничения валидации к типам с помощью аннотаций:
-- Один валидаторlocal x: number @min(0) = 1
-- Несколько валидаторовlocal x: number @min(0) @max(100) = 50
-- Шаблон строкиlocal email: string @pattern("^.+@.+$") = "test@example.com"
-- Валидатор без аргументовlocal x: number @integer = 42Встроенные валидаторы
Заголовок раздела «Встроенные валидаторы»| Валидатор | Применяется к | Пример |
|---|---|---|
@min(n) | number | local x: number @min(0) = 1 |
@max(n) | number | local x: number @max(100) = 50 |
@min_len(n) | string, array | local s: string @min_len(1) = "hi" |
@max_len(n) | string, array | local s: string @max_len(10) = "hi" |
@pattern(regex) | string | local email: string @pattern("^.+@.+$") = "a@b.com" |
Валидаторы полей record
Заголовок раздела «Валидаторы полей record»type User = { age: number @min(0) @max(150), name: string @min_len(1) @max_len(100)}Валидаторы элементов массива
Заголовок раздела «Валидаторы элементов массива»local scores: {number @min(0) @max(100)} = {85, 90}Валидаторы членов union
Заголовок раздела «Валидаторы членов union»local id: number @min(1) | string @min_len(1) = 1Правила вариантности
Заголовок раздела «Правила вариантности»| Позиция | Вариантность | Описание |
|---|---|---|
| Readonly-поле | Ковариантно | Можно использовать подтип |
| Mutable-поле | Инвариантно | Должно совпадать точно |
| Параметр функции | Контравариантно | Можно использовать супертип |
| Возврат функции | Ковариантно | Можно использовать подтип |
Подтипизация
Заголовок раздела «Подтипизация»integer— подтипnumbernever— подтип всех типов- Все типы — подтипы
any - Подтипизация union:
A— подтипA | B
Постепенное внедрение
Заголовок раздела «Постепенное внедрение»Добавляйте типы постепенно — нетипизированный код продолжает работать:
-- Существующий код работает без измененийfunction old_function(x) return x + 1end
-- Новый код получает типыfunction new_function(x: number): number return x + 1endНачните с добавления типов:
- В сигнатурах функций на границах API
- В HTTP-обработчиках и потребителях очередей
- В критической бизнес-логике
Проверка типов
Заголовок раздела «Проверка типов»Запустите проверщик типов:
wippy lintСообщает об ошибках типов без выполнения кода.