Skip to content

Generics ​

Generics let you write types and functions that work with any type, providing flexibility without sacrificing type safety.

Generic Types ​

Generic Records ​

Create records that work with any type:

kettle
type Box[T] = record
  value: T
end record

type Pair[A, B] = record
  first: A
  second: B
end record

Using generic records:

kettle
-- Specify the type parameter when constructing (positional syntax)
int_box: Box[Int] = Box[Int](42)
str_box: Box[String] = Box[String]("hello")

pair: Pair[Int, String] = Pair[Int, String](1, "one")

Generic Variants ​

Variants can also be generic:

kettle
type Maybe[T] = variant
  Just(value: T)
  Nothing
end variant

The built-in Option[T] and Result[T, E] are generic variants:

kettle
type Option[T] = variant
  Some(value: T)
  None
end variant

type Result[T, E] = variant
  Ok(value: T)
  Err(error: E)
end variant

Working with Generic Types ​

kettle
fn unwrap_or_default(box: Box[Int], default: Int) -> Int
  box.value
end fn

-- Define a signature for the mapping function
type IntTransform = (Int) -> Int

fn map_maybe(m: Maybe[Int], f: IntTransform) -> Maybe[Int]
  match m
    Just(v) -> Just(f(v))
    Nothing -> Nothing
  end match
end fn

Const Generics ​

Const generics allow compile-time integer parameters in types. This is useful for fixed-size data structures.

Defining Const Generic Types ​

Use N: Int syntax to declare a const parameter:

kettle
type Vector[N: Int] = record
  data: List[Float]
end record

type Matrix[M: Int, N: Int] = record
  data: List[Float]
end record

Mixing Type and Const Parameters ​

You can combine regular type parameters with const parameters:

kettle
type SizedArray[T, N: Int] = record
  elements: List[T]
  capacity: Int
end record

Using Const Generics ​

kettle
-- Integer literals in type positions (positional construction)
v3: Vector[3] = Vector[3]([1.0, 2.0, 3.0])
v4: Vector[4] = Vector[4]([1.0, 2.0, 3.0, 4.0])

m: Matrix[2, 3] = Matrix[2, 3]([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])

Type Safety with Const Generics ​

Different const values create different types:

kettle
v3: Vector[3] = Vector[3]([1.0, 2.0, 3.0])
v4: Vector[4] = Vector[4]([1.0, 2.0, 3.0, 4.0])

-- This would be a type error:
-- v3 = v4  -- ERROR: Vector[3] and Vector[4] are different types

This catches size mismatches at compile time.

Generic Functions ​

Functions can have type parameters:

kettle
fn identity[T](x: T) -> T
  x
end fn

fn flip_pair[A, B](pair: Tuple[A, B]) -> Tuple[B, A]
  (a, b) = pair
  (b, a)
end fn

fn main() -> Unit using file_io
  -- Type inferred from context
  n: Int = identity(42)
  s: String = identity("hello")

  -- Works with any types
  flipped: Tuple[String, Int] = flip_pair((1, "one"))
  print(to_string(n))
  print(s)
end fn

Type parameters are inferred from usage context. You cannot explicitly specify type parameters at call sites (e.g., identity[Int](42) is not supported).

Note: swap is a reserved builtin for quantum SWAP gates.

See also Effect Polymorphism for functions that work with any effect.

Bounded Polymorphism ​

Type bounds constrain generic parameters to types that support specific operations:

kettle
fn double[T: Numeric](x: T) -> T
  x + x
end fn

fn max[T: Comparable](a: T, b: T) -> T
  match a > b, True -> a, False -> b
end fn

Available Bounds ​

BoundOperationsTypes
Numeric+, -, *, /, unary -Int, Float, QInt
Comparable<, >, <=, >=Int, Float
HasLengthlength()String, List
Lineardiscard()Qubit, QInt

Note: Equality (==, !=) works on all primitive types without a bound.

Example ​

kettle
fn double[T: Numeric](x: T) -> T
  x + x
end fn

fn main() -> Unit using file_io
  print(to_string(double(5)))       -- 10 (Int)
  print(to_string(double(3.14)))    -- 6.28 (Float)
end fn

Next Steps ​