Skip to content

Generics

Language: Español | English


Crespi supports lightweight, duck-typed generics for classes and functions using square bracket syntax.

Generic Classes

Use square brackets to declare type parameters. Use var or let to make constructor parameters into properties:

class Box[T](var value: T) {
    fn get() -> T {
        return this.value
    }

    fn set(newValue: T) {
        this.value = newValue
    }
}

// Usage - type is inferred
var intBox = Box(42)
var strBox = Box("hello")

print(intBox.get())  // 42
print(strBox.get())  // "hello"

Multiple Type Parameters

class Pair[A, B](let first: A, let second: B) {
    fn swap() -> Pair[B, A] {
        return Pair(this.second, this.first)
    }

    fn getFirst() -> A {
        return this.first
    }

    fn getSecond() -> B {
        return this.second
    }
}

var p = Pair(1, "one")
print(p.getFirst())   // 1
print(p.getSecond())  // "one"

var swapped = p.swap()
print(swapped.getFirst())  // "one"

Generic Functions

Functions can also have type parameters. Type parameters go before the function name:

fn [T] identity(x: T) -> T {
    return x
}

print(identity(42))      // 42
print(identity("hello")) // "hello"
print(identity([1,2,3])) // [1, 2, 3]

Multiple Type Parameters in Functions

fn [T, U] transform(value: T, func: (T) -> U) -> U {
    return func(value)
}

fn [T] double(x: Int) -> Int = x * 2
fn [T] toString(x: Int) -> String = str(x)

print(transform(5, double))    // 10
print(transform(42, toString)) // "42"

Generic Constraints

Crespi uses duck typing, so generic types aren't enforced at compile time. The code will work as long as the operations used are valid for the actual types:

class Container[T](var items: List[T] = []) {
    fn add(item: T) {
        this.items.push(item)
    }

    fn getAll() -> List[T] {
        return this.items
    }

    fn count() -> Int {
        return this.items.length()
    }
}

// Works with any type
var numbers = Container()
numbers.add(1)
numbers.add(2)
numbers.add(3)
print(numbers.count())  // 3

var strings = Container()
strings.add("a")
strings.add("b")
print(strings.getAll())  // ["a", "b"]

Generic Methods

Classes with type parameters can have methods that use those parameters:

class Stack[T](var items: List[T] = []) {
    fn push(item: T) {
        this.items.push(item)
    }

    fn pop() -> T? {
        return this.items.pop()
    }

    fn peek() -> T? {
        if this.items.length() == 0 {
            return null
        }
        return this.items[this.items.length() - 1]
    }

    fn isEmpty() -> Bool {
        return this.items.length() == 0
    }
}

var stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)

print(stack.peek())  // 3
print(stack.pop())   // 3
print(stack.pop())   // 2

Nested Generics

You can use generic types within other generic types:

class Box[T](let value: T)

class DoubleBox[A, B](let boxA: Box[A], let boxB: Box[B]) {
    fn getFirst() -> A {
        return this.boxA.value
    }

    fn getSecond() -> B {
        return this.boxB.value
    }
}

var a = Box(10)
var b = Box("hello")
var double = DoubleBox(a, b)

print(double.getFirst())   // 10
print(double.getSecond())  // "hello"

Why Square Brackets?

Crespi uses [T] instead of <T> to avoid ambiguity with comparison operators:

// With angle brackets, this would be ambiguous:
// var x = Foo<Bar>(value)  -- Is this (Foo < Bar) > value ?

// Square brackets are unambiguous:
var x = Foo[Bar](value)  // Clearly a generic instantiation

Generics Behavior

  • Uses square brackets [T, U] to avoid ambiguity with comparison operators
  • Duck-typed: type parameters are parsed but not enforced at runtime
  • Works with both classes and functions
  • Multiple type parameters supported
  • No explicit type instantiation needed (inferred from usage)
  • Generics work in both the interpreter and native compilation

See Also