Type Argument Syntax¶
Guppy makes use of the python’s type parameter syntax, introduced in python 3.12 (see: PEP 695).
This means that type arguments can be given explicitly using square brackets when calling a function. Using this syntax, Guppy function signatures can be written like:
def foo[A, B](a: A, b: B) -> B:
These Guppy functions have type parameters A: Type, B: Type, and a: Type. Their type arguments can be specified explicitly by callers:
foo[int, str](42, "guppy")
Usually these type arguments can be left to be inferred by the type checker.
Bounds¶
Generic functions can have their type bounds specified by an annotation in the type parameter list. In Guppy these bounds can mean three things:
Linearity¶
The type argument can be required to be copyable and/or droppable using the Copy, Drop bounds:
from guppylang import guppy
from guppylang.std.lang import Copy, Drop
@guppy
def copy[T: (Copy, Drop)](t: T) -> tuple[T, T]:
return t, t
Const arguments¶
By default, a type parameter specified in this way represents an arbitrary type. We can also add annotations to the parameters.
These annotations can also be used to specify that the parameter is a const nat parameter, rather than a type:
from guppylang.std.builtins import array
@guppy
def replicate[T: (Copy, Drop), N: nat](elem: T) -> array[T, N]:
return array(elem for _ in range(N))
Protocol bounds¶
Type parameter syntax is used to specify the protocols that a type argument must implement. In the below example, any type argument T to the function foo must implement the MyProto protocols.
Assuming we’ve defined a protocol, MyProto:
@guppy.protocol
class MyProto[T]:
@guppy.require
def foo[T](self, t: T) -> None: ...
We can require that it is implemented by a type arg T by writing:
@guppy
def baz[T: MyProto](t: T) -> None:
return t.foo()
Type Arguments to Classes¶
Guppy structs and protocols can also take type parameters in the same way. The type parameters will be in scope for the signatures of the methods:
@guppy.struct(frozen=True)
class MyStruct[T]:
@guppy
def foo(self, t: T) -> None:
return
@guppy
def bar[S](self, s: S, t: T) -> None:
return self.foo(t)
@guppy
def myfoo() -> None:
return baz(MyStruct[int]())
Type aliases¶
Type variables for Guppy type aliases must still be declared in the python 3.10 style:
T = guppy.type_var("T")
NatPair = guppy.type_alias("NatPair", "tuple[T, nat]", params=[T])
They can then be instantiated with type argument syntax, i.e. in the above example, NatPair[int] is an alias for tuple[int, nat].
This is also works for constant arguments – below QArr[4] is an alias for array[qubit, 4].
N = guppy.nat_var("N")
QArr = guppy.type_alias("QArr", "array[qubit, N]", params=[N])
Caveats¶
Note that Guppy type parameters stand in for one type or constant. Python’s
TypeVarTupleandParamSpecparameters (written as*Tand**P, respectively) aren’t supported.