Skip to content

Generic Functions and Types


Generic Functions

Functions decorated with @blue.generic are called with [] brackets instead of parentheses. These may be used anywhere, but they are intended to help create functions that look like PEP 695 functions with type parameters:

@blue.generic
def add(T):
    def impl(x:T, y: T) -> T:
        return x + y
    return impl

def main() -> None:
    print(add[i32](1, 2))
    print(add[str]('hello ', 'world'))

Like all functions marked @blue, the generic function is guaranteed to be executed at compile-time. We can see in the redshifted version of the above code that add() no longer appears, but the two specialized versions of it remain:

def main() -> None:
    `_print::println[i32]::p`(`t::add[i32]::impl`(1, 2))
    `_print::println[str]::p`(`t::add[str]::impl`('hello ', 'world'))

def `t::add[i32]::impl`(x: i32, y: i32) -> i32:
    return x + y

def `t::add[str]::impl`(x: str, y: str) -> str:
    return `operator::str_add`(x, y)

Generic Class Syntax

@struct classes may also be created with one or more parameters in [] brackets. This is different from passing superclasses inside of () parentheses; rather, this is syntactic sugar for a generic function with an inner @struct class than can make use of those parameters:

@struct
class MyList[T]:
    inner: list[T]
    other_param_1: ...
    other_param_2: ...

# ↑ is syntactic sugar for ↓

@blue.generic
def MyList(T):
    @struct
    class Self:
        inner: list[T]
        other_param_1: ...
        other_param_2: ...

    return Self

In use, this looks like:

@struct
class MyNamedList[T]:
    name: str
    data: list[T]

def main() -> None:
    my_int_list = MyNamedList[i32]("profits", [])
    my_int_list.data.append(1_000_000)

    my_str_list = MyNamedList[str]("words", ["hello", "world"])
    my_str_list.data.extend(["and", "goodbye"])

For a larger example, see the myarray example on GitHub or run it in the SPy Playground

__origin__

The __origin__ attribute of SPy objects carries information about the generic function which created them, if any. When blue.generic defines a type or function and returns it, the returned object has it's __origin__ is set to the generic function:

@blue.generic
def adder(T):
    @struct
    class impl:
         ...

    return impl


def main() -> None:
    assert adder[T].__origin__ is adder

This is a straightforward way to identify that, for example, MyList[T] is a 'specialised' version of MyList on the type T.

(The default value for __origin__ is None. If the object returned by a generic function already has a non-None origin, that origin will not be overwritten.)

The __origin__ functions identically with the Generic Class syntax described above:

@struct
class MyList[T]:
    inner: list[T]

def main() -> None:
    assert MyList[i32].__origin__ is MyList