Generics
Also known as: generic template type parameter parametric polymorphism type variable
● Intermediate5 minBefore this: Functions, RecordsSpec 2.9
A generic function or type works for any type: fn first<T>(items: [T]) -> T? works on a list of anything.
fn first<T>(items: [T]) -> T? {
items.get(0)
}
type Pair<A, B> {
left: A
right: B
}
fn main() {
print(first([3, 1, 2]) ?? 0)
print(first(["x", "y"]) ?? "")
const p = Pair(left: "age", right: 31)
print("{p.left}: {p.right}")
}The program ran and printed:
kavach run generics-fn.kav
3xage: 31
<T> is a placeholder for a type. Each call fills it in: first([3, 1, 2]) uses Int, first(["x", "y"]) uses String. Types like Pair<A, B> work the same way. A method can have its own placeholders too:
type Picker {
seed: Int
fn pick<T>(self, items: [T]) -> T {
items[self.seed % items.len()]
}
}
fn first<T>(xs: [T]) -> T? {
xs.get(0)
}
fn apply(f: fn([Int]) -> Int?, xs: [Int]) -> Int {
f(xs) ?? -1
}
fn main() {
const p = Picker(seed: 5)
print(p.pick([10, 20, 30]))
print(p.pick(["x", "y"]))
print(apply(first, [7, 8]))
}The program ran and printed:
kavach run generics-methods.kav
30y7
Type arguments written where a value is made
A generic type's arguments are usually worked out from its fields and from where the value goes. When no field shows them, write them where the value is made, or give the variable its type. Inside the type's methods, I is then the type the value was made with:
use std.json
type Shout {
text: String
}
/// JSON kept as text until it is needed. No field holds an `I`,
/// so `I` is written where a box is made.
type Box<I> {
raw: String
fn get(self) -> I ! JsonError {
try json.decode<I>(self.raw)
}
}
fn main() ! Error {
const a = Box<Shout>(raw: "\{\"text\":\"hi\"}")
const b: Box<Shout> = Box(raw: "\{\"text\":\"there\"}")
print("{(try a.get()).text} {(try b.get()).text}")
}The program ran and printed:
kavach run generics-construction.kav
hi thereThe count must match the type's parameters:
type Box<I> {
raw: String
}
fn main() {
const c = Box<String, Int>(raw: "")
print(c.raw)
}The compiler refused the program:
kavach run generics-arity.kav
error: `Box` takes 1 type argument, not 2Go deeper the why, for readers who want it
A generic function is compiled once for each type it is used with, so it runs as fast as if you had written a copy for that type yourself. To hold values of different types together in one list instead, use an interface value.