型システム
試験的。 一部の制限が予想されます。
Wippy には、フローセンシティブなチェックを伴う段階的型システムが含まれています。型はデフォルトで非 nullable です。
プリミティブ
Section titled “プリミティブ”local n: number = 3.14local i: integer = 42 -- integer は number のサブタイプlocal s: string = "hello"local b: boolean = truelocal a: any = "anything" -- 明示的な動的型(チェックを無効化)local u: unknown = something -- 使用前にナローイングが必要any vs unknown
Section titled “any vs unknown”-- any: 型チェックを無効化local a: any = get_data()a.foo.bar.baz() -- エラーなし、ランタイムでクラッシュする可能性がある
-- unknown: 安全な未知の型、使用前にナローイングが必要local u: unknown = get_data()u.foo -- エラー: unknown のプロパティにはアクセスできませんif type(u) == "table" then -- ここで u は table にナローイングされるendnil 安全性
Section titled “nil 安全性”型はデフォルトで非 nullable です。任意の値には ? を使用します:
local x: number = nil -- エラー: nil は number に代入できないlocal y: number? = nil -- OK: number? は「number または nil」を意味するlocal z: number? = 42 -- OK制御フローのナローイング
Section titled “制御フローのナローイング”型チェッカーは制御フローを追跡します:
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 は非 nil にナローイングされる
-- またはデフォルト値local val = get_value() or 0 -- val: numberlocal val: number | string = get_value()
if type(val) == "number" then print(val + 1) -- val: numberelse print(val:upper()) -- val: stringendtype 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 * 2end可変長引数関数
Section titled “可変長引数関数”local function sum(...: number): number local total: number = 0 for _, v in ipairs({...}) do total = total + v end return totalendtype User = {name: string, age: number}
local u: User = {name = "alice", age = 25}オプションフィールド
Section titled “オプションフィールド”type Config = { host: string, port: number, timeout?: number, debug?: boolean}
local cfg: Config = {host = "localhost", port = 8080} -- OKジェネリクス
Section titled “ジェネリクス”local function identity<T>(x: T): T return xend
local n: number = identity(42)local s: string = identity("hello")制約付きジェネリクス
Section titled “制約付きジェネリクス”type HasName = {name: string}
local function greet<T: HasName>(obj: T): string return "Hello, " .. obj.nameend
greet({name = "Alice"}) -- OKgreet({age = 30}) -- エラー: 'name' がないインターセクション型
Section titled “インターセクション型”複数の型を組み合わせます:
type Named = {name: string}type Aged = {age: number}type Person = Named & Aged
local p: Person = {name = "Alice", age = 30}タグ付きユニオン
Section titled “タグ付きユニオン”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 endendnever 型
Section titled “never 型”never はボトム型です — 値は存在しません:
function fail(msg: string): never error(msg)endエラー処理パターン
Section titled “エラー処理パターン”チェッカーは Lua のエラーイディオムを理解します:
local value, err = call()if err then -- ここで value は nil return nil, errend-- ここで value は非 nil、err は nilprint(value)非 nil アサーション
Section titled “非 nil アサーション”! を使用して式が非 nil であることをアサートします:
local user: User? = get_user()local name = user!.name -- user が非 nil であることをアサートランタイム時に値が nil の場合、エラーが発生します。値が nil でないことが分かっているが、型チェッカーがそれを証明できない場合に使用します。
安全なキャスト(検証)
Section titled “安全なキャスト(検証)”検証してキャストするために、型を関数として呼び出します:
local data: any = get_json()local user = User(data) -- 検証して User を返すlocal name = user.name -- 安全なフィールドアクセスプリミティブ型とカスタム型の両方で動作します:
local x: any = get_value()local s = string(x) -- string にキャストlocal n = integer(x) -- integer にキャストlocal b = boolean(x) -- boolean にキャスト
type Point = {x: number, y: number}local p = Point(data) -- レコード構造を検証するType:is() メソッド
Section titled “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 は Point にナローイングされるend安全でないキャスト
Section titled “安全でないキャスト”チェックなしのキャストには :: または as を使用します:
local data: any = get_data()local user = data :: User -- ランタイムチェックなしlocal user = data as User -- :: と同じ控えめに使用してください。安全でないキャストは検証をバイパスし、値が型に一致しない場合にランタイムエラーを引き起こす可能性があります。
型のリフレクション
Section titled “型のリフレクション”型はイントロスペクションメソッドを持つファーストクラスの値です。
print(Number:kind()) -- "number"print(Point:kind()) -- "record"print(Point:name()) -- "Point"レコードフィールド
Section titled “レコードフィールド”レコードフィールドを反復処理します:
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"コレクション型
Section titled “コレクション型”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"オプション型
Section titled “オプション型”local opt: number? = nillocal optType = typeof(opt)print(optType:kind()) -- "optional"print(optType:inner():kind()) -- "number"type Status = "pending" | "active" | "done"
for variant in Status:variants() do print(variant)endlocal 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 (厳密なサブタイプ)テーブルキーとしての型
Section titled “テーブルキーとしての型”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}型バリデータ
Section titled “型バリデータ”注釈を使用して型にランタイム検証制約を追加します:
-- 単一バリデータ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組み込みバリデータ
Section titled “組み込みバリデータ”| バリデータ | 適用対象 | 例 |
|---|---|---|
@min(n) | number | local x: number @min(0) = 1 |
@max(n) | number | local x: number @max(100) = 50 |
@min_len(n) | string、配列 | local s: string @min_len(1) = "hi" |
@max_len(n) | string、配列 | local s: string @max_len(10) = "hi" |
@pattern(regex) | string | local email: string @pattern("^.+@.+$") = "a@b.com" |
レコードフィールドバリデータ
Section titled “レコードフィールドバリデータ”type User = { age: number @min(0) @max(150), name: string @min_len(1) @max_len(100)}配列要素バリデータ
Section titled “配列要素バリデータ”local scores: {number @min(0) @max(100)} = {85, 90}ユニオンメンバーバリデータ
Section titled “ユニオンメンバーバリデータ”local id: number @min(1) | string @min_len(1) = 1変性のルール
Section titled “変性のルール”| 位置 | 変性 | 説明 |
|---|---|---|
| 読み取り専用フィールド | 共変 | サブタイプを使用可能 |
| ミュータブルフィールド | 不変 | 完全に一致する必要がある |
| 関数パラメータ | 反変 | スーパータイプを使用可能 |
| 関数戻り値 | 共変 | サブタイプを使用可能 |
サブタイピング
Section titled “サブタイピング”integerはnumberのサブタイプneverはすべての型のサブタイプ- すべての型は
anyのサブタイプ - ユニオンサブタイピング:
AはA | Bのサブタイプ
段階的な導入
Section titled “段階的な導入”型を段階的に追加します — 型のないコードは引き続き動作します:
-- 既存のコードは変更なしで動作function old_function(x) return x + 1end
-- 新しいコードに型を付けるfunction new_function(x: number): number return x + 1end以下の箇所から型を追加し始めます:
- API 境界の関数シグネチャ
- HTTP ハンドラとキューコンシューマ
- 重要なビジネスロジック
型チェッカーを実行します:
wippy lintコードを実行せずに型エラーをレポートします。