Generics
Generics let a record, enum or machine work over any type, fixed at each use site. Lake uses monomorphisation: for every concrete type a generic is used with, the compiler emits a specialised copy — there is no boxing and no runtime type information, so generic code is exactly as fast as hand-written code.
§Generic syntax: square brackets
Type parameters are written in [ ]:
Box[T] is {
value T
}
unbox[T] is {
b Box[T] -> ret T {
ret b.value
}
}Box[i64] and Box[buf] become two distinct concrete records at
compile time; unbox is specialised for each.
§Using generic types
let b Box[i64] = Box(42)
let n = unbox(b) # 42The annotation Box[i64] fixes T. Where the compiler can infer the
type parameter from the arguments it does so automatically; where it
cannot (for example a zero-argument constructor), annotate the binding.
§The collections are generic
The standard library's containers are generic over their element type:
+std.vec.{ Vec vec_new vec_push vec_get }
main is {
_ -> {
let v0 Vec[i64] = vec_new()
let v1 = vec_push(v0 10)
let v2 = vec_push(v1 20)
let x = vec_get(v2 1) # 20
}
}Vec[i64], Vec[Point], IntMap[buf] are all separate
monomorphised types. See the standard library for the
full container set.
§Bounds: constraining a type parameter
A type parameter can be required to satisfy a protocol:
same[T: Eq] is {
a T b T -> ret i64 {
ret eq(a b)
}
}[T: Eq] means “same accepts any T that implements Eq.” The
compiler verifies the bound at each instantiation: if you call same
with a type that has no eq, it is a compile error, not a runtime one.
§Key ideas
- Type parameters use square brackets:
Vec[T],unbox[T],same[T: Eq]. - Monomorphisation specialises per concrete type — zero-cost, no boxing.
- Inference fixes
Tfrom arguments; annotate where it can't (e.g.let v Vec[i64] = vec_new()). - Bounds (
[T: Eq]) constrain a parameter to a protocol, checked at compile time.