Generics
Generic classes
Declared with one or more type parameters in angle brackets:
class Box<T> {
value: T
}
def main() {
let b = new Box<Int> { value: 42 }
print(b.value.Str()) // 42
}
Multiple type parameters:
class Pair<A, B> {
first: A,
second: B
}
def main() {
let p = new Pair<Int, Str> { first: 1, second: "one" }
print(p.first.Str()) // 1
print(p.second) // one
}
Type parameters are in scope for all field declarations and method bodies within the class.
Providing the wrong concrete type for a generic field is a TypeError.
Generic methods on generic classes
Instance methods on a generic class can introduce their own additional type parameters:
class Wrapper<T> {
data: T,
def map<U>(self, f: Fn(T) U) Wrapper<U> ->
new Wrapper<U> { data: f(self.data) }
}
def main() {
let w = new Wrapper<Int> { data: 5 }
let s = w.map!<Str>(fn (n: Int) Str -> n.Str())
print(s.data) // 5
}
A method-level type parameter must not duplicate the class-level parameter name — this is a
TypeError.
Generic functions
def identity<T>(x: T) T -> x
def main() {
print(identity!<Int>(42).Str()) // 42
print(identity!<Str>("hello")) // hello
}
Multiple type parameters:
def apply<T, A>(t: T, f: Fn(T) A) A -> f(t)
def main() {
const x = 2
print(apply!<Int, Int>(x, fn (n: Int) -> n + 1).Str()) // 3
}
Instantiation syntax
Generic functions and methods are called with !<Type> between the name and the argument list:
def main() {
identity!<Int>(42)
List.empty!<Int>()
// list.map!<Str>(fn (n: Int, i: Int) -> n.Str())
}
Omitting the !<Type> on a generic function is a TypeError. Providing the wrong number
of type arguments is also a TypeError.
First-class generic functions
Generic functions can be stored in variables and instantiated through the variable:
def f<T>(a: T) T -> a
def main() {
let f_ = f
print(f_!<Str>("abc")) // abc
}
Nested generics and chaining
class Box<T> { value: T }
def main() {
let nested = new Box<Box<Int>> {
value: new Box<Int> { value: 1 }
}
print(nested.value.value.Str()) // 1
}
Chained generic method calls:
def main() {
let result = List.empty!<Int>()
.map!<Str>(fn (n: Int, i: Int) -> n.Str())
.map!<Int>(fn (s: Str, i: Int) -> s.len())
}
Type parameters are not first-class
Passing an unknown type T to a function that does not declare it is an UnknownSymbol:
def a<T>(x: T) T { return x }
def main() {
// a!<T>("") // UnknownSymbol: T is not defined at the call site
}