Typsystem
Typsystem
Abschnitt betitelt „Typsystem“Experimentell. Einige Einschränkungen sind zu erwarten.
Wippy enthält ein graduelles Typsystem mit flusssensitiver Prüfung. Typen sind standardmäßig nicht-nullbar.
Primitive
Abschnitt betitelt „Primitive“local n: number = 3.14local i: integer = 42 -- integer is subtype of numberlocal s: string = "hello"local b: boolean = truelocal a: any = "anything" -- explicit dynamic (opt-out of checking)local u: unknown = something -- must narrow before useany vs. unknown
Abschnitt betitelt „any vs. unknown“-- any: opt-out of type checkinglocal a: any = get_data()a.foo.bar.baz() -- no error, may crash at runtime
-- unknown: safe unknown, must narrow before uselocal u: unknown = get_data()u.foo -- ERROR: cannot access property of unknownif type(u) == "table" then -- u narrowed to table hereendNil-Sicherheit
Abschnitt betitelt „Nil-Sicherheit“Typen sind standardmäßig nicht-nullbar. Verwende ? für optionale Werte:
local x: number = nil -- ERROR: nil not assignable to numberlocal y: number? = nil -- OK: number? means "number or nil"local z: number? = 42 -- OKKontrollfluss-Verfeinerung
Abschnitt betitelt „Kontrollfluss-Verfeinerung“Der Typprüfer verfolgt den Kontrollfluss:
local function process(x: number?): number if x ~= nil then return x -- x is number here end return 0end
-- Early return patternlocal user, err = get_user(123)if err then return nil, err end-- user narrowed to non-nil here
-- Or defaultlocal val = get_value() or 0 -- val: numberUnion-Typen
Abschnitt betitelt „Union-Typen“local val: number | string = get_value()
if type(val) == "number" then print(val + 1) -- val: numberelse print(val:upper()) -- val: stringendLiteral-Typen
Abschnitt betitelt „Literal-Typen“type Status = "pending" | "active" | "done"
local s: Status = "pending" -- OKlocal s: Status = "invalid" -- ERRORFunktionstypen
Abschnitt betitelt „Funktionstypen“local function add(a: number, b: number): number return a + bend
-- Multiple returnslocal function div_mod(a: number, b: number): (number, number) return math.floor(a / b), a % bend
-- Error returns (Lua idiom)local function fetch(url: string): (string?, error?) -- returns (data, nil) or (nil, error)end
-- First-class function typeslocal double: (number) -> number = function(x: number): number return x * 2endVariadische Funktionen
Abschnitt betitelt „Variadische Funktionen“local function sum(...: number): number local total: number = 0 for _, v in ipairs({...}) do total = total + v end return totalendRecord-Typen
Abschnitt betitelt „Record-Typen“type User = {name: string, age: number}
local u: User = {name = "alice", age = 25}Optionale Felder
Abschnitt betitelt „Optionale Felder“type Config = { host: string, port: number, timeout?: number, debug?: boolean}
local cfg: Config = {host = "localhost", port = 8080} -- OKGenerics
Abschnitt betitelt „Generics“local function identity<T>(x: T): T return xend
local n: number = identity(42)local s: string = identity("hello")Eingeschränkte Generics
Abschnitt betitelt „Eingeschränkte Generics“type HasName = {name: string}
local function greet<T: HasName>(obj: T): string return "Hello, " .. obj.nameend
greet({name = "Alice"}) -- OKgreet({age = 30}) -- ERROR: missing 'name'Intersection-Typen
Abschnitt betitelt „Intersection-Typen“Mehrere Typen kombinieren:
type Named = {name: string}type Aged = {age: number}type Person = Named & Aged
local p: Person = {name = "Alice", age = 30}Tagged Unions
Abschnitt betitelt „Tagged Unions“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 endendDer never-Typ
Abschnitt betitelt „Der never-Typ“never ist der Bottom-Typ — es existieren keine Werte:
function fail(msg: string): never error(msg)endFehlerbehandlungs-Muster
Abschnitt betitelt „Fehlerbehandlungs-Muster“Der Prüfer versteht das Lua-Fehler-Idiom:
local value, err = call()if err then -- value is nil here return nil, errend-- value is non-nil here, err is nilprint(value)Non-Nil-Assertion
Abschnitt betitelt „Non-Nil-Assertion“Verwende !, um zu beteuern, dass ein Ausdruck nicht nil ist:
local user: User? = get_user()local name = user!.name -- assert user is non-nilWenn der Wert zur Laufzeit nil ist, wird ein Fehler ausgelöst. Verwende dies, wenn du weißt, dass ein Wert nicht nil sein kann, der Typprüfer dies aber nicht beweisen kann.
Typ-Casts
Abschnitt betitelt „Typ-Casts“Sicherer Cast (Validierung)
Abschnitt betitelt „Sicherer Cast (Validierung)“Rufe einen Typ als Funktion auf, um zu validieren und zu casten:
local data: any = get_json()local user = User(data) -- validates and returns Userlocal name = user.name -- safe field accessFunktioniert mit Primitiven und benutzerdefinierten Typen:
local x: any = get_value()local s = string(x) -- cast to stringlocal n = integer(x) -- cast to integerlocal b = boolean(x) -- cast to boolean
type Point = {x: number, y: number}local p = Point(data) -- validates record structureType:is()-Methode
Abschnitt betitelt „Type:is()-Methode“Validiert ohne zu werfen, gibt (value, nil) oder (nil, error) zurück:
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 is valid Pointelse return nil, err -- validation failedendDas Ergebnis verfeinert sich in Conditionals:
if Point:is(data) then local p: Point = data -- data narrowed to PointendUnsicherer Cast
Abschnitt betitelt „Unsicherer Cast“Verwende :: oder as für ungeprüfte Casts:
local data: any = get_data()local user = data :: User -- no runtime checklocal user = data as User -- same as ::Sparsam verwenden. Unsichere Casts umgehen die Validierung und können Laufzeitfehler verursachen, wenn der Wert nicht zum Typ passt.
Typ-Reflektion
Abschnitt betitelt „Typ-Reflektion“Typen sind First-Class-Werte mit Introspektionsmethoden.
Kind und Name
Abschnitt betitelt „Kind und Name“print(Number:kind()) -- "number"print(Point:kind()) -- "record"print(Point:name()) -- "Point"Record-Felder
Abschnitt betitelt „Record-Felder“Über Record-Felder iterieren:
type User = {name: string, age: number}
for name, typ in User:fields() do print(name, typ:kind())end-- name string-- age numberAuf einzelne Feldtypen zugreifen:
local nameType = User.name -- type of 'name' fieldprint(nameType:kind()) -- "string"Collection-Typen
Abschnitt betitelt „Collection-Typen“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"Optionale Typen
Abschnitt betitelt „Optionale Typen“local opt: number? = nillocal optType = typeof(opt)print(optType:kind()) -- "optional"print(optType:inner():kind()) -- "number"Union-Typen
Abschnitt betitelt „Union-Typen“type Status = "pending" | "active" | "done"
for variant in Status:variants() do print(variant)endFunktionstypen
Abschnitt betitelt „Funktionstypen“local fn: (number, string) -> boolean
local fnType = typeof(fn)for param in fnType:params() do print(param:kind())endprint(fnType:ret():kind()) -- "boolean"Typ-Vergleich
Abschnitt betitelt „Typ-Vergleich“print(Number == Number) -- trueprint(Integer <= Number) -- true (subtype)print(Integer < Number) -- true (strict subtype)Typen als Tabellenschlüssel
Abschnitt betitelt „Typen als Tabellenschlüssel“local handlers = {}handlers[Number] = function() return "number handler" endhandlers[String] = function() return "string handler" end
local h = handlers[typeof(value)]if h then h() endTyp-Annotationen
Abschnitt betitelt „Typ-Annotationen“Typen zu Funktionssignaturen hinzufügen:
-- Parameter and return typeslocal function process(input: string): number return #inputend
-- Local variable typeslocal count: number = 0
-- Type aliasestype StringArray = {string}type StringMap = {[string]: number}Typ-Validatoren
Abschnitt betitelt „Typ-Validatoren“Füge Typen Laufzeit-Validierungs-Constraints über Annotationen hinzu:
-- Single validatorlocal x: number @min(0) = 1
-- Multiple validatorslocal x: number @min(0) @max(100) = 50
-- String patternlocal email: string @pattern("^.+@.+$") = "test@example.com"
-- No-arg validatorlocal x: number @integer = 42Eingebaute Validatoren
Abschnitt betitelt „Eingebaute Validatoren“| Validator | Gilt für | Beispiel |
|---|---|---|
@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" |
Validatoren für Record-Felder
Abschnitt betitelt „Validatoren für Record-Felder“type User = { age: number @min(0) @max(150), name: string @min_len(1) @max_len(100)}Validatoren für Array-Elemente
Abschnitt betitelt „Validatoren für Array-Elemente“local scores: {number @min(0) @max(100)} = {85, 90}Validatoren für Union-Mitglieder
Abschnitt betitelt „Validatoren für Union-Mitglieder“local id: number @min(1) | string @min_len(1) = 1Varianzregeln
Abschnitt betitelt „Varianzregeln“| Position | Varianz | Beschreibung |
|---|---|---|
| Readonly-Feld | Kovariant | Subtyp erlaubt |
| Veränderliches Feld | Invariant | Muss exakt übereinstimmen |
| Funktionsparameter | Kontravariant | Supertyp erlaubt |
| Funktions-Rückgabe | Kovariant | Subtyp erlaubt |
Subtyping
Abschnitt betitelt „Subtyping“integerist ein Subtyp vonnumberneverist ein Subtyp aller Typen- Alle Typen sind Subtypen von
any - Union-Subtyping:
Aist Subtyp vonA | B
Schrittweise Einführung
Abschnitt betitelt „Schrittweise Einführung“Typen inkrementell hinzufügen — untypisierter Code funktioniert weiterhin:
-- Existing code works unchangedfunction old_function(x) return x + 1end
-- New code gets typesfunction new_function(x: number): number return x + 1endBeginne damit, Typen hinzuzufügen zu:
- Funktionssignaturen an API-Grenzen
- HTTP-Handler und Queue-Konsumenten
- Kritischer Geschäftslogik
Typprüfung
Abschnitt betitelt „Typprüfung“Den Typprüfer ausführen:
wippy lintMeldet Typfehler, ohne Code auszuführen.