3.1 KiB
Generics and type expressions
This document covers generics, bounds, unions/intersections, and the rules for type expressions in Lyng.
Generic parameters
Declare type parameters with <...> on functions and classes:
fun id<T>(x: T): T = x
class Box<T>(val value: T)
Type arguments are usually inferred at call sites:
val b = Box(10) // Box<Int>
val s = id("ok") // T is String
Bounds
Use : to set bounds. Bounds may be unions (|) or intersections (&):
fun sum<T: Int | Real>(x: T, y: T) = x + y
class Named<T: Iterable & Comparable>(val data: T)
Bounds are checked at compile time. For union bounds, the argument must fit at least one option. For intersection bounds, it must fit all options.
Variance
Generic types are invariant by default. You can specify declaration-site variance:
class Source<out T>(val value: T)
class Sink<in T> { fun accept(x: T) { ... } }
out makes the type covariant (produced), in makes it contravariant (consumed).
Inference rules
- Literals set obvious types (
1isInt,1.0isReal, etc.). - Empty list literals default to
List<Object>unless constrained by context. - Non-empty list literals infer element type as a union of element types.
Examples:
val a = [1, 2, 3] // List<Int>
val b = [1, "two", true] // List<Int | String | Bool>
val c: List<Int> = [] // List<Int>
Spreads propagate element type when possible:
val base = [1, 2]
val mix = [...base, 3] // List<Int>
Type expressions
Type expressions include simple types, generics, unions, and intersections:
Int
List<String>
Int | String
Iterable & Comparable
These type expressions can appear in casts and is checks.
is, in, and == with type expressions
There are two categories of is checks:
-
Value checks:
x is Txis a value,Tis a type expression.- This is a runtime instance check.
-
Type checks:
T1 is T2- both sides are type expressions (class objects or unions/intersections).
- This is a type-subset check: every value of
T1must fit inT2.
Exact type expression equality uses == and is structural (union/intersection order does not matter).
Includes checks use in with type expressions:
A in T
This means A is a subset of T (the same relation as A is T).
Examples (T = A | B):
T == A // false
T is A // false
A in T // true
B in T // true
T is A | B // true
Practical examples
fun acceptInts<T: Int>(xs: List<T>) { }
acceptInts([1, 2, 3])
// acceptInts([1, "a"]) -> compile-time error
fun f<T>(list: List<T>) {
assert( T is Int | String | Bool )
assert( !(T is Int) )
assert( Int in T )
}
f([1, "two", true])
Notes
Tis reified as a type expression when needed (e.g., union/intersection). When it is a single class,Tis that class object.- Type expression checks are compile-time where possible; runtime checks only happen for
ison values and explicit casts.