Generic Syntax
Type parameters follow a type or function name. Each parameter names a type within that declaration.
class Box<T> {
T value
}
T first<T>(List<T> values) {
return values[0]
}Bounds
T maximum<T extends Comparable<T>>(T left, T right) {
if left.compareTo(other: right) >= 0 { return left }
return right
}Each type parameter currently accepts only one nominal extends upper bound; there is no syntax for intersecting several bounds. The bound may be a class, an interface, or a previously declared type parameter. U extends T is validated after substituting the outer type arguments.
Default types
Type parameters may declare default types. Defaulted parameters must form a contiguous suffix, may refer only to earlier declared type parameters, and must satisfy their own upper bounds.
enum Result<T, E = String> {
Ok(T value, String msg = ""),
Err(E error, String msg = "")
}Result<Integer> equals Result<Integer, String>. Explicit Result<Integer, Failure> overrides the default. Defaults expand during semantic analysis; Core IR, the NAR public ABI, and runtime types still record both complete actual type arguments. This is not a raw type.
Class bounds are satisfied through class inheritance; interface bounds through explicitly declared implements or interface extends relationships. Matching members do not establish a relationship. Calls and bound method values for class methods within a constraint preserve virtual dispatch; interface method calls use dynamic interface dispatch.
Use
A type position must supply all required arguments: raw types are invalid, and only trailing parameters declaring defaults may be omitted. A function or instance-method call may omit explicit type arguments when constraints give a unique solution; otherwise use function<Type>(...) or receiver.method<Type>(...) respectively.
Actual type arguments enter Core IR and the runtime type environment. Parameterized types are invariant. A diamond constructor omits only arguments uniquely solvable from constraints in that expression.
This executable example combines generic construction, inference, overload selection, and matching retained type arguments:
value Envelope<T> {
T item
String origin
}
String describe(Integer value) {
return "number: ${value}"
}
String describe(String value) {
return "text: " + value
}
Envelope<T> wrap<T>(T item, String origin = "local") {
return Envelope<T>(item: item, origin: origin)
}
String classify(Any candidate) {
return switch candidate {
case Envelope<Integer> box { break describe(value: box.item) }
case Envelope<String> box { break describe(value: box.item) }
case _ { break "other" }
}
}
String nested(Any candidate) {
return switch candidate {
case Envelope<List<Integer>> box { break "list: ${box.item.size()}" }
case _ { break "other" }
}
}
main() {
Envelope<Integer> count = wrap(item: 7)
Envelope<String> name = wrap(item: "Norm", origin: "user")
printLine(classify(candidate: count))
printLine(classify(candidate: name))
printLine(name.origin)
Any unknown = wrap(item: true)
printLine(classify(candidate: unknown))
Envelope<List<Integer>> wrapped = wrap(item: [1, 2, 3])
printLine(nested(candidate: wrapped))
Envelope<List<String>> words = wrap(item: ["one", "two"])
printLine(nested(candidate: words))
}number: 7
text: Norm
user
other
list: 3
other