Generics
Generics let you write a record, enum, or machine once and reuse it across many element types. Type parameters appear in square brackets after the name. Lake resolves them by monomorphisation: every concrete use site is compiled into its own specialized copy, so there is no runtime cost.
Generic Records
Add [T] (or several parameters) to a record declaration. Fields may then
use the type parameter:
Box[T] is { val T }
Pair[K V] is { k K v V }
Construction infers the type parameters from the argument types:
main is {
_ -> {
let b = Box(42) -- Box[i64]
let p = Pair(1 "hello") -- Pair[i64 buf]
}
}
Generic Machines
A machine can be generic too. Its parameters are bound from the call
arguments and may appear in the parameter types and the ret type:
unbox[T] is {
b Box[T] -> ret T {
ret b.val
}
}
main is {
_ -> {
let b = Box(42)
let v = unbox(b) -- T = i64, v : i64
}
}
A trivial identity machine shows the shape clearly:
id[T] is {
x T -> ret T { ret x }
}
Generic Enums
Enums accept type parameters in exactly the same way. Option[T] and
Result[T E] are the canonical examples (covered in the
Enums chapter):
Option[T] is enum {
Some(T)
None
}
Result[T E] is enum {
Ok(T)
Err(E)
}
unwrap_or[T] is {
o Option[T] d T -> ret T {
when o {
Some(v) -> { ret v }
None -> { ret d }
}
}
}
Use-Site Types and Annotations
Write a concrete instantiation by filling the brackets: Vec[i64],
Box[i64], Result[i64 buf]. You often need this on let bindings whose
right-hand side does not, on its own, pin down the type parameters — for
example a constructor that returns an empty collection, or a payload-free
enum variant:
main is {
_ -> {
let v Vec[i64] = vec_new() -- vec_new() has no element to infer from
let y Option[i64] = Option.None -- None carries no payload
let r Result[i64 buf] = Result.Ok(7)
}
}
When the arguments already determine the type — like Box(42) or
Option.Some(42) — the annotation is optional.
Two Generics, Same Name
Because dispatch is by argument type, two generic machines that share a
name can coexist as long as they live in different modules and take
distinguishable argument types. The compiler picks the right one from the
call’s argument types. This is how unwrap_or works for both Option[T]
(in std.option) and Result[T E] (in std.result).
Notes and Limitations
- Monomorphisation happens at compile time; each distinct instantiation becomes its own specialized function. There is no boxing or dynamic dispatch.
- Value/return inference sometimes needs an explicit annotation on an
intermediate binding. If the compiler can’t infer a type parameter,
annotate the
let(as above). IntMap[V]is keyed byi64. A fully generic hash map (arbitrary key types) is not available yet — see the Standard Library chapter.