Types & optionals¶
wick is statically typed with inference. Every expression has a type at compile time; annotations are only needed where inference has nothing to work with.
The types¶
| Type | Values | Notes |
|---|---|---|
num |
IEEE-754 double | the only number type, like Lua |
bool |
true, false |
the only type conditions accept |
str |
immutable text | + concatenates str+str only |
list<T> |
ordered, 0-based | T is num, bool, str, or a record |
map<T> |
string-keyed | values are num, bool, or str |
| record | named fields | see Records |
T? |
T or nil |
an optional — the headline feature |
nil is not a general value: it only inhabits optionals. let x = nil
does not compile without an annotation (let x: str? = nil).
Optionals: nil-safety, enforced¶
Any operation that can fail returns an optional, and the compiler will
not let you touch the value until you have dealt with the nil case.
let s = lt.load_save("hi") // s: str?
lt.print(s, 4, 4, 1, 1, 1, 1) // COMPILE ERROR: str? where str expected
Two ways to deal with it:
1. ?? — unwrap with a default
?? evaluates its right side only when the left is nil, and the result
is the non-optional base type.
2. Narrowing — if x != nil
Narrowing works on local variables; module globals must use ??.
Assigning a new optional value inside the block un-narrows.
What returns an optional¶
| Expression | Type | nil when |
|---|---|---|
lt.load_save(k) |
str? |
no readable save |
num(s) |
num? |
s is not a number (including "") |
pop(xs) |
T? |
the list is empty |
m[key] |
T? |
the key is absent |
env(name) |
str? |
the variable is unset |
The bug this kills
In the Lua build of Lantern Night, an interrupted save left a 0-byte
file; lt.load_save returned "" (truthy!), the or "0" fallback
never fired, and tonumber("") produced a nil that crashed frame 1.
The wick equivalent — num(lt.load_save(k) ?? "") ?? 0 — is the only
version the compiler accepts.
Assignment compatibility¶
Tmay be stored whereT?is expected (widening is free).T?may not be stored whereTis expected — unwrap first.- Containers are invariant:
list<num>only acceptsnumelements;list<Prop>only accepts that record type. void(a function without a return type) cannot be assigned at all; calling a void function is a statement, not an expression.
Handles are still num¶
Mesh, texture, and sound handles from lt.load_* are plain num today.
Passing a texture id to lt.play type-checks; wrong use is a runtime
no-op or error. Branded handle types are a candidate if store games need
them — not yet admitted.