GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing peps.python.org — PEP 695 – Type Parameter Syntax | peps.python.org
PEP 695 – Type Parameter Syntax
PEP 695 – Type Parameter Syntax
Table of Contents
Important This PEP is a historical document: see Variance Inference, Type aliases, Type parameter lists, The type statement and Annotation scopes. for up-to-date specs and documentation. Canonical typing specs are maintained at the typing specs site; runtime typing behaviour is described in the CPython documentation. × See the typing specification update process for how to propose changes to the typing spec. AbstractThis PEP specifies an improved syntax for specifying type parameters within a generic class, function, or type alias. It also introduces a new statement for declaring type aliases. MotivationPEP 484 introduced type variables into the language. PEP 612 built upon this concept by introducing parameter specifications, and PEP 646 added variadic type variables. While generic types and type parameters have grown in popularity, the syntax for specifying type parameters still feels “bolted on” to Python. This is a source of confusion among Python developers. There is consensus within the Python static typing community that it is time to provide a formal syntax that is similar to other modern programming languages that support generic types. An analysis of 25 popular typed Python libraries revealed that type
variables (in particular, the Points of ConfusionWhile the use of type variables has become widespread, the manner in which they are specified within code is the source of confusion among many Python developers. There are a couple of factors that contribute to this confusion. The scoping rules for type variables are difficult to understand. Type variables are typically allocated within the global scope, but their semantic meaning is valid only when used within the context of a generic class, function, or type alias. A single runtime instance of a type variable may be reused in multiple generic contexts, and it has a different semantic meaning in each of these contexts. This PEP proposes to eliminate this source of confusion by declaring type parameters at a natural place within a class, function, or type alias declaration statement. Generic type aliases are often misused because it is not clear to developers
that a type argument must be supplied when the type alias is used. This leads
to an implied type argument of PEP 483 and PEP 484 introduced the concept of “variance” for a type variable used within a generic class. Type variables can be invariant, covariant, or contravariant. The concept of variance is an advanced detail of type theory that is not well understood by most Python developers, yet they must confront this concept today when defining their first generic class. This PEP largely eliminates the need for most developers to understand the concept of variance when defining generic classes. When more than one type parameter is used with a generic class or type alias,
the rules for type parameter ordering can be confusing. It is normally based on
the order in which they first appear within a class or type alias declaration
statement. However, this can be overridden in a class definition by
including a “Generic” or “Protocol” base class. For example, in the class
declaration The practice of sharing a type variable across multiple generic contexts creates other problems today. Modern editors provide features like “find all references” and “rename all references” that operate on symbols at the semantic level. When a type parameter is shared among multiple generic classes, functions, and type aliases, all references are semantically equivalent. Type variables defined within the global scope also need to be given a name
that starts with an underscore to indicate that the variable is private to
the module. Globally-defined type variables are also often given names to
indicate their variance, leading to cumbersome names like “_T_contra” and
“_KT_co”. The current mechanisms for allocating type variables also requires
the developer to supply a redundant name in quotes (e.g. Defining type parameters today requires importing the Summary ExamplesDefining a generic class prior to this PEP looks something like this. from typing import Generic, TypeVar _T_co = TypeVar("_T_co", covariant=True, bound=str) class ClassA(Generic[_T_co]): def method1(self) -> _T_co: ... With the new syntax, it looks like this. class ClassA[T: str]: def method1(self) -> T: ... Here is an example of a generic function today. from typing import TypeVar _T = TypeVar("_T") def func(a: _T, b: _T) -> _T: ... And the new syntax. def func[T](a: T, b: T) -> T: ... Here is an example of a generic type alias today. from typing import TypeAlias _T = TypeVar("_T") ListOrSet: TypeAlias = list[_T] | set[_T] And with the new syntax. type ListOrSet[T] = list[T] | set[T] SpecificationType Parameter DeclarationsHere is a new syntax for declaring type parameters for generic classes, functions, and type aliases. The syntax adds support for a comma-delimited list of type parameters in square brackets after the name of the class, function, or type alias. Simple (non-variadic) type variables are declared with an unadorned name.
Variadic type variables are preceded by # This generic class is parameterized by a TypeVar T, a # TypeVarTuple Ts, and a ParamSpec P. class ChildClass[T, *Ts, **P]: ... There is no need to include class ClassA[T](Generic[T]): ... # Runtime error A class ClassA[S, T](Protocol): ... # OK class ClassB[S, T](Protocol[S, T]): ... # Recommended type checker error Type parameter names within a generic class, function, or type alias must be unique within that same class, function, or type alias. A duplicate name generates a syntax error at compile time. This is consistent with the requirement that parameter names within a function signature must be unique. class ClassA[T, *T]: ... # Syntax Error def func1[T, **T](): ... # Syntax Error Class type parameter names are mangled if they begin with a double
underscore, to avoid complicating the name lookup mechanism for names used
within the class. However, the Upper Bound SpecificationFor a non-variadic type parameter, an “upper bound” type can be specified
through the use of a type annotation expression. If an upper bound is
not specified, the upper bound is assumed to be class ClassA[T: str]: ... The specified upper bound type must use an expression form that is allowed in type annotations. More complex expression forms should be flagged as an error by a type checker. Quoted forward references are allowed. The specified upper bound type must be concrete. An attempt to use a generic
type should be flagged as an error by a type checker. This is consistent with
the existing rules enforced by type checkers for a class ClassA[T: dict[str, int]]: ... # OK class ClassB[T: "ForwardReference"]: ... # OK class ClassC[V]: class ClassD[T: dict[str, V]]: ... # Type checker error: generic type class ClassE[T: [str, int]]: ... # Type checker error: illegal expression form Constrained Type SpecificationPEP 484 introduced the concept of a “constrained type variable” which is constrained to a set of two or more types. The new syntax supports this type of constraint through the use of a literal tuple expression that contains two or more types. class ClassA[AnyStr: (str, bytes)]: ... # OK class ClassB[T: ("ForwardReference", bytes)]: ... # OK class ClassC[T: ()]: ... # Type checker error: two or more types required class ClassD[T: (str, )]: ... # Type checker error: two or more types required t1 = (bytes, str) class ClassE[T: t1]: ... # Type checker error: literal tuple expression required If the specified type is not a tuple expression or the tuple expression includes complex expression forms that are not allowed in a type annotation, a type checker should generate an error. Quoted forward references are allowed. class ClassF[T: (3, bytes)]: ... # Type checker error: invalid expression form The specified constrained types must be concrete. An attempt to use a generic
type should be flagged as an error by a type checker. This is consistent with
the existing rules enforced by type checkers for a class ClassG[T: (list[S], str)]: ... # Type checker error: generic type Runtime Representation of Bounds and ConstraintsThe upper bounds and constraints of Generic Type AliasWe propose to introduce a new statement for declaring type aliases. Similar
to # A non-generic type alias type IntOrStr = int | str # A generic type alias type ListOrSet[T] = list[T] | set[T] Type aliases can refer to themselves without the use of quotes. # A type alias that includes a forward reference type AnimalOrVegetable = Animal | "Vegetable" # A generic self-referential type alias type RecursiveList[T] = T | list[RecursiveList[T]] The Type parameters declared as part of a generic type alias are valid only when evaluating the right-hand side of the type alias. As with Type alias expressions are not allowed to use traditional type variables (i.e.
those allocated with an explicit T = TypeVar("T") type MyList = list[T] # Type checker error: traditional type variable usage We propose to deprecate the existing Runtime Type Alias ClassAt runtime, a
All of these attributes are read-only. The value of the type alias is evaluated lazily (see Lazy Evaluation below). Type Parameter ScopesWhen the new syntax is used, a new lexical scope is introduced, and this scope includes the type parameters. Type parameters can be accessed by name within inner scopes. As with other symbols in Python, an inner scope can define its own symbol that overrides an outer-scope symbol of the same name. This section provides a verbal description of the new scoping rules. The Scoping Behavior section below specifies the behavior in terms of a translation to near-equivalent existing Python code. Type parameters are visible to other type parameters declared elsewhere in the list. This allows type parameters to use other type parameters within their definition. While there is currently no use for this capability, it preserves the ability in the future to support upper bound expressions or type argument defaults that depend on earlier type parameters. A compiler error or runtime exception is generated if the definition of an earlier type parameter references a later type parameter even if the name is defined in an outer scope. # The following generates no compiler error, but a type checker # should generate an error because an upper bound type must be concrete, # and ``Sequence[S]`` is generic. Future extensions to the type system may # eliminate this limitation. class ClassA[S, T: Sequence[S]]: ... # The following generates no compiler error, because the bound for ``S`` # is lazily evaluated. However, type checkers should generate an error. class ClassB[S: Sequence[T], T]: ... A type parameter declared as part of a generic class is valid within the class body and inner scopes contained therein. Type parameters are also accessible when evaluating the argument list (base classes and any keyword arguments) that comprise the class definition. This allows base classes to be parameterized by these type parameters. Type parameters are not accessible outside of the class body, including class decorators. class ClassA[T](BaseClass[T], param = Foo[T]): ... # OK print(T) # Runtime error: 'T' is not defined @dec(Foo[T]) # Runtime error: 'T' is not defined class ClassA[T]: ... A type parameter declared as part of a generic function is valid within the function body and any scopes contained therein. It is also valid within parameter and return type annotations. Default argument values for function parameters are evaluated outside of this scope, so type parameters are not accessible in default value expressions. Likewise, type parameters are not in scope for function decorators. def func1[T](a: T) -> T: ... # OK print(T) # Runtime error: 'T' is not defined def func2[T](a = list[T]): ... # Runtime error: 'T' is not defined @dec(list[T]) # Runtime error: 'T' is not defined def func3[T](): ... A type parameter declared as part of a generic type alias is valid within the type alias expression. type Alias1[K, V] = Mapping[K, V] | Sequence[K] Type parameter symbols defined in outer scopes cannot be bound with
S = 0 def outer1[S](): S = 1 T = 1 def outer2[T](): def inner1(): nonlocal S # OK because it binds variable S from outer1 nonlocal T # Syntax error: nonlocal binding not allowed for type parameter def inner2(): global S # OK because it binds variable S from global scope The lexical scope introduced by the new type parameter syntax is unlike
traditional scopes introduced by a class Outer: class Private: pass # If the type parameter scope was like a traditional scope, # the base class 'Private' would not be accessible here. class Inner[T](Private, Sequence[T]): pass # Likewise, 'Inner' would not be available in these type annotations. def method1[T](self, a: Inner[T]) -> Inner[T]: return a The compiler allows inner scopes to define a local symbol that overrides an outer-scoped type parameter. Consistent with the scoping rules defined in PEP 484, type checkers should generate an error if inner-scoped generic classes, functions, or type aliases reuse the same type parameter name as an outer scope. T = 0 @decorator(T) # Argument expression `T` evaluates to 0 class ClassA[T](Sequence[T]): T = 1 # All methods below should result in a type checker error # "type parameter 'T' already in use" because they are using the # type parameter 'T', which is already in use by the outer scope # 'ClassA'. def method1[T](self): ... def method2[T](self, x = T): # Parameter 'x' gets default value of 1 ... def method3[T](self, x: T): # Parameter 'x' has type T (scoped to method3) ... Symbols referenced in inner scopes are resolved using existing rules except that type parameter scopes are also considered during name resolution. T = 0 # T refers to the global variable print(T) # Prints 0 class Outer[T]: T = 1 # T refers to the local variable scoped to class 'Outer' print(T) # Prints 1 class Inner1: T = 2 # T refers to the local type variable within 'Inner1' print(T) # Prints 2 def inner_method(self): # T refers to the type parameter scoped to class 'Outer'; # If 'Outer' did not use the new type parameter syntax, # this would instead refer to the global variable 'T' print(T) # Prints 'T' def outer_method(self): T = 3 # T refers to the local variable within 'outer_method' print(T) # Prints 3 def inner_func(): # T refers to the variable captured from 'outer_method' print(T) # Prints 3 When the new type parameter syntax is used for a generic class, assignment
expressions are not allowed within the argument list for the class definition.
Likewise, with functions that use the new type parameter syntax, assignment
expressions are not allowed within parameter or return type annotations, nor
are they allowed within the expression that defines a type alias, or within
the bounds and constraints of a This restriction is necessary because expressions evaluated within the new lexical scope should not introduce symbols within that scope other than the defined type parameters, and should not affect whether the enclosing function is a generator or coroutine. class ClassA[T]((x := Sequence[T])): ... # Syntax error: assignment expression not allowed def func1[T](val: (x := int)): ... # Syntax error: assignment expression not allowed def func2[T]() -> (x := Sequence[T]): ... # Syntax error: assignment expression not allowed type Alias1[T] = (x := list[T]) # Syntax error: assignment expression not allowed Accessing Type Parameters at RuntimeA new attribute called Type parameters declared using the new syntax will not appear within the
dictionary returned by Variance InferenceThis PEP eliminates the need for variance to be specified for type parameters. Instead, type checkers will infer the variance of type parameters based on their usage within a class. Type parameters are inferred to be invariant, covariant, or contravariant depending on how they are used. Python type checkers already include the ability to determine the variance of type parameters for the purpose of validating variance within a generic protocol class. This capability can be used for all classes (whether or not they are protocols) to calculate the variance of each type parameter. The algorithm for computing the variance of a type parameter is as follows. For each type parameter in a generic class: 1. If the type parameter is variadic ( 2. If the type parameter comes from a traditional 3. Create two specialized versions of the class. We’ll refer to these as
4. Determine whether Here is an example. class ClassA[T1, T2, T3](list[T1]): def method1(self, a: T2) -> None: ... def method2(self) -> T3: ... To determine the variance of upper = ClassA[object, Dummy, Dummy] lower = ClassA[T1, Dummy, Dummy] We find that To determine the variance of upper = ClassA[Dummy, object, Dummy] lower = ClassA[Dummy, T2, Dummy] Since To determine the variance of upper = ClassA[Dummy, Dummy, object] lower = ClassA[Dummy, Dummy, T3] Since Auto Variance For TypeVarThe existing A generic class that uses the traditional syntax may include combinations of type variables with explicit and inferred variance. T1 = TypeVar("T1", infer_variance=True) # Inferred variance T2 = TypeVar("T2") # Invariant T3 = TypeVar("T3", covariant=True) # Covariant # A type checker should infer the variance for T1 but use the # specified variance for T2 and T3. class ClassA(Generic[T1, T2, T3]): ... Compatibility with Traditional TypeVarsThe existing mechanism for allocating It is OK to combine traditional type variables with new-style type parameters if the class, function, or type alias does not use the new syntax. The new-style type parameters must come from an outer scope in this case. K = TypeVar("K") class ClassA[V](dict[K, V]): ... # Type checker error class ClassB[K, V](dict[K, V]): ... # OK class ClassC[V]: # The use of K and V for "method1" is OK because it uses the # "traditional" generic function mechanism where type parameters # are implicit. In this case V comes from an outer scope (ClassC) # and K is introduced implicitly as a type parameter for "method1". def method1(self, a: V, b: K) -> V | K: ... # The use of M and K are not allowed for "method2". A type checker # should generate an error in this case because this method uses the # new syntax for type parameters, and all type parameters associated # with the method must be explicitly declared. In this case, ``K`` # is not declared by "method2", nor is it supplied by a new-style # type parameter defined in an outer scope. def method2[M](self, a: M, b: K) -> M | K: ... Runtime ImplementationGrammar ChangesThis PEP introduces a new soft keyword
type_params: '[' t=type_param_seq ']' type_param_seq: a[asdl_typeparam_seq*]=','.type_param+ [','] type_param: | a=NAME b=[type_param_bound] | '*' a=NAME | '**' a=NAME type_param_bound: ":" e=expression # Grammar definitions for class_def_raw and function_def_raw are modified # to reference type_params as an optional syntax element. The definitions # of class_def_raw and function_def_raw are simplified here for brevity. class_def_raw: 'class' n=NAME t=[type_params] ... function_def_raw: a=[ASYNC] 'def' n=NAME t=[type_params] ...
type_alias: "type" n=NAME t=[type_params] '=' b=expression AST ChangesThis PEP introduces a new AST node type called TypeAlias(expr name, typeparam* typeparams, expr value) It also adds an AST node type that represents a type parameter. typeparam = TypeVar(identifier name, expr? bound)
| ParamSpec(identifier name)
| TypeVarTuple(identifier name)
Bounds and constraints are represented identically in the AST. In the implementation,
any expression that is a It also modifies existing AST node types Lazy EvaluationThis PEP introduces three new contexts where expressions may occur that represent
static types: To prevent a similar situation with the new syntax proposed in this PEP, we propose
to use lazy evaluation for these expressions, similar to the approach in PEP 649.
Specifically, each expression will be saved in a code object, and the code object
is evaluated only when the corresponding attribute is accessed ( If PEP 649 is implemented, additional evaluation mechanisms should be added to
mirror the options that PEP provides for annotations. In the current version of the
PEP, that might include adding an As a consequence of lazy evaluation, the value observed for an attribute may depend on the time the attribute is accessed. X = int class Foo[T: X, U: X]: t, u = T, U print(Foo.t.__bound__) # prints "int" X = str print(Foo.u.__bound__) # prints "str" Similar examples affecting type annotations can be constructed using the semantics of PEP 563 or PEP 649. A naive implementation of lazy evaluation would handle class namespaces incorrectly, because functions within a class do not normally have access to the enclosing class namespace. The implementation will retain a reference to the class namespace so that class-scoped names are resolved correctly. Scoping BehaviorThe new syntax requires a new kind of scope that behaves differently from existing scopes in Python. Thus, the new syntax cannot be described exactly in terms of existing Python scoping behavior. This section specifies these scopes further by reference to existing scoping behavior: the new scopes behave like function scopes, except for a number of minor differences listed below. All examples include functions introduced with the pseudo-keyword
In the below translations, names that start with two underscores are internal to the implementation and not visible to actual Python code. We use the following intrinsic functions, which in the real implementation are defined directly in the interpreter:
Non-generic type aliases are translated as follows: type Alias = int Equivalent to: def695 __evaluate_Alias(): return int Alias = __make_typealias(name='Alias', evaluate_value=__evaluate_Alias) Generic type aliases: type Alias[T: int] = list[T] Equivalent to: def695 __generic_parameters_of_Alias(): def695 __evaluate_T_bound(): return int T = __make_typevar_with_bound(name='T', evaluate_bound=__evaluate_T_bound) def695 __evaluate_Alias(): return list[T] return __make_typealias(name='Alias', type_params=(T,), evaluate_value=__evaluate_Alias) Alias = __generic_parameters_of_Alias() Generic functions: def f[T](x: T) -> T: return x Equivalent to: def695 __generic_parameters_of_f(): T = typing.TypeVar(name='T') def f(x: T) -> T: return x f.__type_params__ = (T,) return f f = __generic_parameters_of_f() A fuller example of generic functions, illustrating the scoping behavior of defaults, decorators, and bounds.
Note that this example does not use @decorator def f[T: int, U: (int, str), *Ts, **P]( x: T = SOME_CONSTANT, y: U, *args: *Ts, **kwargs: P.kwargs, ) -> T: return x Equivalent to: __default_of_x = SOME_CONSTANT # evaluated outside the def695 scope def695 __generic_parameters_of_f(): def695 __evaluate_T_bound(): return int T = __make_typevar_with_bound(name='T', evaluate_bound=__evaluate_T_bound) def695 __evaluate_U_constraints(): return (int, str) U = __make_typevar_with_constraints(name='U', evaluate_constraints=__evaluate_U_constraints) Ts = typing.TypeVarTuple("Ts") P = typing.ParamSpec("P") def f(x: T = __default_of_x, y: U, *args: *Ts, **kwargs: P.kwargs) -> T: return x f.__type_params__ = (T, U, Ts, P) return f f = decorator(__generic_parameters_of_f()) Generic classes: class C[T](Base): def __init__(self, x: T): self.x = x Equivalent to: def695 __generic_parameters_of_C(): T = typing.TypeVar('T') class C(Base): __type_params__ = (T,) def __init__(self, x: T): self.x = x return C C = __generic_parameters_of_C() The biggest divergence from existing behavior for class C: class Nested: ... def generic_method[T](self, x: T, y: Nested) -> T: ... Equivalent to: class C: class Nested: ... def695 __generic_parameters_of_generic_method(): T = typing.TypeVar('T') def generic_method(self, x: T, y: Nested) -> T: ... return generic_method generic_method = __generic_parameters_of_generic_method() In this example, the annotations for Library ChangesSeveral classes in the Reference ImplementationThis proposal is prototyped in CPython PR #103764. The Pyright type checker supports the behavior described in this PEP. Rejected IdeasPrefix ClauseWe explored various syntactic options for specifying type parameters that
preceded using S, T class ClassA: ... This option was rejected because the scoping rules for the type parameters were less clear. Also, this syntax did not interact well with class and function decorators, which are common in Python. Only one other popular programming language, C++, uses this approach. We likewise considered prefix forms that looked like decorators (e.g.,
Angle BracketsMany languages that support generics make use of angle brackets. (Refer to
the table at the end of Appendix A for a summary.) We explored the use of
angle brackets for type parameter declarations in Python, but we ultimately
rejected it for two reasons. First, angle brackets are not considered
“paired” by the Python scanner, so end-of-line characters between a Bounds SyntaxWe explored various syntactic options for specifying the bounds and constraints
for a type variable. We considered, but ultimately rejected, the use
of a Explicit VarianceWe considered adding syntax for specifying whether a type parameter is intended
to be invariant, covariant, or contravariant. The Name ManglingWhen considering implementation options, we considered a “name mangling” approach where each type parameter was given a unique “mangled” name by the compiler. This mangled name would be based on the qualified name of the generic class, function or type alias it was associated with. This approach was rejected because qualified names are not necessarily unique, which means the mangled name would need to be based on some other randomized value. Furthermore, this approach is not compatible with techniques used for evaluating quoted (forward referenced) type annotations. Appendix A: Survey of Type Parameter SyntaxSupport for generic types is found in many programming languages. In this section, we provide a survey of the options used by other popular programming languages. This is relevant because familiarity with other languages will make it easier for Python developers to understand this concept. We provide additional details here (for example, default type argument support) that may be useful when considering future extensions to the Python type system. C++C++ uses angle brackets in combination with keywords C++20 introduced the notion of generalized constraints, which can act
like protocols in Python. A collection of constraints can be defined in
a named entity called a Variance is not explicitly specified, but constraints can enforce variance. A default type argument can be specified using the // Generic class template <typename T> class ClassA { // Constraints are supported through compile-time assertions. static_assert(std::is_base_of<BaseClass, T>::value); public: Container<T> t; }; // Generic function with default type argument template <typename S = int> S func1(ClassA<S> a, S b) {}; // C++20 introduced a more generalized notion of "constraints" // and "concepts", which are named constraints. // A sample concept template<typename T> concept Hashable = requires(T a) { { std::hash<T>{}(a) } -> std::convertible_to<std::size_t>; }; // Use of a concept in a template template<Hashable T> void func2(T value) {} // Alternative use of concept template<typename T> requires Hashable<T> void func3(T value) {} // Alternative use of concept template<typename T> void func3(T value) requires Hashable<T> {} JavaJava uses angle brackets to declare type parameters and for specialization.
By default, type parameters are invariant.
The Java uses use-site variance. The compiler places limits on which methods and members can be accessed based on the use of a generic type. Variance is not specified explicitly. Java provides no way to specify a default type argument. // Generic class public class ClassA<T> { public Container<T> t; // Generic method public <S extends Number> void method1(S value) { } // Use site variance public void method1(ClassA<? super Integer> value) { } } C#C# uses angle brackets to declare type parameters and for specialization.
The C# uses declaration-site variance using the keywords C# provides no way to specify a default type argument. // Generic class with bounds on type parameters public class ClassA<S, T> where T : SomeClass1 where S : SomeClass2 { // Generic method public void MyMethod<U>(U value) where U : SomeClass3 { } } // Contravariant and covariant type parameters public class ClassB<in S, out T> { public T MyMethod(S value) { } } TypeScriptTypeScript uses angle brackets to declare type parameters and for
specialization. The TypeScript uses declaration-site variance. Variance is inferred from
usage, not specified explicitly. TypeScript 4.7 introduced the ability
to specify variance using A default type argument can be specified using the TypeScript supports the // Generic interface interface InterfaceA<S, T extends SomeInterface1> { val1: S; val2: T; method1<U extends SomeInterface2>(val: U): S } // Generic function function func1<T, K extends keyof T>(ojb: T, key: K) { } // Contravariant and covariant type parameters (TypeScript 4.7) interface InterfaceB<in S, out T> { } // Type parameter with default interface InterfaceC<T = SomeInterface3> { } // Generic type alias type MyType<T extends SomeInterface4> = Array<T> ScalaIn Scala, square brackets are used to declare type parameters. Square
brackets are also used for specialization. The Scala uses use-site variance but also allows declaration-site variance
specification. It uses a Scala provides no way to specify a default type argument. It does support higher-kinded types (type parameters that accept type type parameters). // Generic class; type parameter has upper bound class ClassA[A <: SomeClass1] { // Generic method; type parameter has lower bound def method1[B >: A](val: B) ... } // Use of an upper and lower bound with the same type parameter class ClassB[A >: SomeClass1 <: SomeClass2] { } // Contravariant and covariant type parameters class ClassC[+A, -B] { } // Higher-kinded type trait Collection[T[_]] { def method1[A](a: A): T[A] def method2[B](b: T[B]): B } // Generic type alias type MyType[T <: Int] = Container[T] SwiftSwift uses angle brackets to declare type parameters and for specialization. The upper bound of a type parameter is specified using a colon. Swift doesn’t support generic variance; all type parameters are invariant. Swift provides no way to specify a default type argument. // Generic class class ClassA<T> { // Generic method func method1<X>(val: T) -> X { } } // Type parameter with upper bound constraint class ClassB<T: SomeClass1> {} // Generic type alias typealias MyType<A> = Container<A> RustRust uses angle brackets to declare type parameters and for specialization.
The upper bound of a type parameter is specified using a colon. Alternatively
a Rust does not have traditional object oriented inheritance or variance. Subtyping in Rust is very restricted and occurs only due to variance with respect to lifetimes. A default type argument can be specified using the // Generic class struct StructA<T> { // T's lifetime is inferred as covariant x: T } fn f<'a>( mut short_lifetime: StructA<&'a i32>, mut long_lifetime: StructA<&'static i32>, ) { long_lifetime = short_lifetime; // error: StructA<&'a i32> is not a subtype of StructA<&'static i32> short_lifetime = long_lifetime; // valid: StructA<&'static i32> is a subtype of StructA<&'a i32> } // Type parameter with bound struct StructB<T: SomeTrait> {} // Type parameter with additional constraints struct StructC<T> where T: Iterator, T::Item: Copy {} // Generic function fn func1<T>(val: &[T]) -> T { } // Generic type alias type MyType<T> = StructC<T>; KotlinKotlin uses angle brackets to declare type parameters and for specialization.
By default, type parameters are invariant. The upper bound of a type is
specified using a colon.
Alternatively, a Kotlin supports declaration-site variance where variance of type parameters is
explicitly declared using Kotlin provides no way to specify a default type argument. // Generic class class ClassA<T> // Type parameter with upper bound class ClassB<T : SomeClass1> // Contravariant and covariant type parameters class ClassC<in S, out T> // Generic function fun <T> func1(): T { // Use site variance val covariantA: ClassA<out Number> val contravariantA: ClassA<in Number> } // Generic type alias typealias TypeAliasFoo<T> = ClassA<T> JuliaJulia uses curly braces to declare type parameters and for specialization.
The # Generic struct; type parameter with upper and lower bounds # Valid for T in (Int64, Signed, Integer, Real, Number) struct Container{Int <: T <: Number} x::T end # Generic function function func1(v::Container{T}) where T <: Real end # Alternate forms of generic function function func2(v::Container{T} where T <: Real) end function func3(v::Container{<: Real}) end # Tuple types are covariant # Valid for func4((2//3, 3.5)) function func4(t::Tuple{Real,Real}) end DartDart uses angle brackets to declare type parameters and for specialization.
The upper bound of a type is specified using the Dart supports declaration-site variance, where variance of type parameters is
explicitly declared using Dart provides no way to specify a default type argument. // Generic class class ClassA<T> { } // Type parameter with upper bound class ClassB<T extends SomeClass1> { } // Contravariant and covariant type parameters class ClassC<in S, out T> { } // Generic function T func1<T>() { } // Generic type alias typedef TypeDefFoo<T> = ClassA<T>; GoGo uses square brackets to declare type parameters and for specialization.
The upper bound of a type is specified after the name of the parameter, and
must always be specified. The keyword Go doesn’t support variance; all type parameters are invariant. Go provides no way to specify a default type argument. Go does not support generic type aliases. // Generic type without a bound type TypeA[T any] struct { t T } // Type parameter with upper bound type TypeB[T SomeType1] struct { } // Generic function func func1[T any]() { } Summary
AcknowledgementsThanks to Sebastian Rittau for kick-starting the discussions that led to this proposal, to Jukka Lehtosalo for proposing the syntax for type alias statements and to Jelle Zijlstra, Daniel Moisset, and Guido van Rossum for their valuable feedback and suggested improvements to the specification and implementation. CopyrightThis document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |