Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PEP 695: Fix/improve syntax & content of Language Survey section #2725

Merged
merged 5 commits into from
Aug 28, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 134 additions & 57 deletions pep-0695.rst
Original file line number Diff line number Diff line change
Expand Up @@ -840,19 +840,19 @@ may be useful when considering future extensions to the Python type system.
C++
---

C++ uses angle brackets in combination with keywords "template" and
"typename" to declare type parameters. It uses angle brackets for
C++ uses angle brackets in combination with keywords ``template`` and
``typename`` to declare type parameters. It uses angle brackets for
specialization.

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 "concept".
a named entity called a ``concept``.

Variance is not explicitly specified, but constraints can enforce variance.

A default type argument can be specified using the "=" operator.
A default type argument can be specified using the ``=`` operator.

::
.. code-block:: c++

// Generic class
template <typename>
Expand Down Expand Up @@ -896,39 +896,44 @@ Java
----

Java uses angle brackets to declare type parameters and for specialization.
The "extends" keyword is used to specify an upper bound.
By default, type parameters are invariant.
The ``extends`` keyword is used to specify an upper bound. The ``super`` keyword
is used to specify a contravariant bound.

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.

::
.. code-block:: java

// Generic class
public class ClassA<T> {
public Container<T> t;

// Generic method
public <S extends Number> void method1(S value) { }

// Use site variance
KotlinIsland marked this conversation as resolved.
Show resolved Hide resolved
public void method1(ClassA<? super Integer> value) { }
}


C#
--

C# uses angle brackets to declare type parameters and for specialization.
The "where" keyword and a colon is used to specify the bound for a type
The ``where`` keyword and a colon is used to specify the bound for a type
parameter.

C# uses declaration-site variance using the keywords "in" and "out" for
C# uses declaration-site variance using the keywords ``in`` and ``out`` for
contravariance and covariance, respectively. By default, type parameters are
invariant.

C# provides no way to specify a default type argument.

::
.. code-block:: c#

// Generic class with bounds on type parameters
public class ClassA<S, T>
Expand All @@ -951,20 +956,21 @@ TypeScript
----------

TypeScript uses angle brackets to declare type parameters and for
specialization. The "extends" keyword is used to specify a bound. It can be
combined with other type operators such as "keyof".
specialization. The ``extends`` keyword is used to specify a bound. It can be
combined with other type operators such as ``keyof``.

TypeScript uses declaration-site variance. Variance is inferred from
usage, not specified explicitly. TypeScript 4.7 will introduce the ability
to specify variance using "in" and "out" keywords. This was added to handle
extremely complex types where inference of variance was expensive.
usage, not specified explicitly. TypeScript 4.7 introduced the ability
to specify variance using ``in`` and ``out`` keywords. This was added to handle
extremely complex types where inference of variance was expensive,
yet the maintainers state that is useful for increasing readability.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Readability was not the motivation for adding this capability. I meet often with the TypeScript team, and I'm very familiar with their motivations and the debates that led to their (reluctant) decision to add this capability.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't a motivation, but my edit here was based on information from the documentation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you provide some references for these reasons? Reading the pr microsoft/TypeScript#48240, it seems to lead with positive motivations and nothing negative that I can see:

Indeed, when generic type instantiations are related structurally, variance annotations serve no purpose. This is why TypeScript strictly doesn't need variance annotations. However, variance annotations are useful to assert desired type relations of their subject generic types.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This information comes from direct conversations with Anders Hejlsberg, Ryan Cavanaugh, and Daniel Rosenwasser, the leads on the TypeScript team. I've discussed the arguments you've raised about variance. Here's how Daniel summarized their advice:
"our perspective is that ideally we wouldn't have added [explicit] variance if we didn't need to"


A default type argument can be specified using the "=" operator.
A default type argument can be specified using the ``=`` operator.

TypeScript supports the "type" keyword to declare a type alias, and this
TypeScript supports the ``type`` keyword to declare a type alias, and this
syntax supports generics.

::
.. code-block:: typescript

// Generic interface
interface InterfaceA<S, T extends SomeInterface1> {
Expand All @@ -991,19 +997,19 @@ Scala
-----

In Scala, square brackets are used to declare type parameters. Square
brackets are also used for specialization. The "<:" and ">:" operators
brackets are also used for specialization. The ``<:`` and ``>:`` operators
are used to specify upper and lower bounds, respectively.

Scala uses use-site variance but also allows declaration-site variance
specification. It uses a "+" or "-" prefix operator for covariance and
specification. It uses a ``+`` or ``-`` prefix operator for covariance and
contravariance, respectively.

Scala provides no way to specify a default type argument.

It does support higher-kinded types (type parameters that accept type
type parameters).

::
.. code-block:: scala


// Generic class; type parameter has upper bound
Expand Down Expand Up @@ -1036,17 +1042,16 @@ Swift
Swift uses angle brackets to declare type parameters and for specialization.
The upper bound of a type parameter is specified using a colon.

Swift uses declaration-site variance, and variance of type parameters is
inferred from their usage.
Swift doesn't support generic variance; all type parameters are invariant.

Swift provides no way to specify a default type argument.

::
.. code-block:: swift

// Generic class
class ClassA<T> {
// Generic method
func method1<X>(val: T) -> S { }
func method1<X>(val: T) -> X { }
}

// Type parameter with upper bound constraint
Expand All @@ -1061,23 +1066,33 @@ Rust

Rust uses angle brackets to declare type parameters and for specialization.
The upper bound of a type parameter is specified using a colon. Alternatively
a "where" clause can specify various constraints.
a ``where`` clause can specify various constraints.

Rust uses declaration-site variance, and variance of type parameters is
typically inferred from their usage. In cases where a type parameter is not
used within a type, variance can be specified explicitly.
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 "=" operator.
A default type argument can be specified using the ``=`` operator.

::
.. code-block:: rust

// Generic class
struct StructA<T> {
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: StructA> {}
struct StructB<T: SomeTrait> {}

// Type parameter with additional constraints
struct StructC<T>
Expand All @@ -1089,56 +1104,55 @@ A default type argument can be specified using the "=" operator.
// Generic function
fn func1<T>(val: &[T]) -> T { }

// Explicit variance specification
use type_variance::{Covariant, Contravariant};

struct StructD<A, R> {
arg: Covariant<A>,
ret: Contravariant<R>,
}

// Generic type alias
type MyType<T> = StructC<T>
type MyType<T> = StructC<T>;


Kotlin
------

Kotlin uses angle brackets to declare type parameters and for specialization.
The upper bound of a type is specified using a colon.
By default, type parameters are invariant. The upper bound of a type is
specified using a colon.
Alternatively, a ``where`` clause can specify various constraints.

Kotlin supports declaration-site variance where variance of type parameters is
explicitly declared using "in" and "out" keywords. It also supports use-site
explicitly declared using ``in`` and ``out`` keywords. It also supports use-site
variance which limits which methods and members can be used.

Kotlin provides no way to specify a default type argument.

::
.. code-block:: kotlin

// Generic class
class ClassA<T> { }
class ClassA<T>

// Type parameter with upper bound
class ClassB<T: SomeClass1> { }
class ClassB<T : SomeClass1>

// Contravariant and covariant type parameters
class ClassC<in S, out T> { }
class ClassC<in S, out T>

// Generic function
fun func1<T>() -> T {}
fun <T> func1(): T {

// Use site variance
val covariantA: ClassA<out Number>
val contravariantA: ClassA<in Number>
}

// Generic type alias
typealias<T> = ClassA<T>
typealias TypeAliasFoo<T> = ClassA<T>


Julia
-----

Julia uses curly braces to declare type parameters and for specialization.
The "<:" operator can be used within a "where" clause to declare
The ``<:`` operator can be used within a ``where`` clause to declare
upper and lower bounds on a type.

::
.. code-block:: julia

// Generic struct; type parameter with upper and lower bounds
struct StructA{T} where Int <: T <: Number
Expand All @@ -1151,6 +1165,62 @@ upper and lower bounds on a type.
// Alternate form of generic function
function func2(v::Container{T} where T <: Real)

Dart
----

Dart uses angle brackets to declare type parameters and for specialization.
The upper bound of a type is specified using the ``extends`` keyword.
By default, type parameters are covariant.

Dart supports declaration-site variance, where variance of type parameters is
explicitly declared using ``in``, ``out`` and ``inout`` keywords.
It does not support use-site variance.

Dart provides no way to specify a default type argument.

.. code-block:: dart

// 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>;

Go
--

Go 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 ``any`` is used for an unbound type parameter.

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.

.. code-block:: go

// 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
-------
Expand All @@ -1162,7 +1232,8 @@ Summary
| C++ | template | n/a | n/a | = | n/a | n/a |
| | <> | | | | | |
+------------+----------+---------+--------+----------+-----------+-----------+
| Java | <> | extends | | | use | inferred |
| Java | <> | extends | | | use | super, |
| | | | | | | extends |
+------------+----------+---------+--------+----------+-----------+-----------+
| C# | <> | where | | | decl | in, out |
+------------+----------+---------+--------+----------+-----------+-----------+
Expand All @@ -1171,15 +1242,21 @@ Summary
+------------+----------+---------+--------+----------+-----------+-----------+
| Scala | [] | T <: X | T >: X | | use, decl | +, - |
+------------+----------+---------+--------+----------+-----------+-----------+
| Swift | <> | T: X | | | decl | inferred |
| Swift | <> | T: X | | | n/a | n/a |
+------------+----------+---------+--------+----------+-----------+-----------+
| Rust | <> | T: X, | | = | decl | inferred, |
| | | where | | | | explicit |
| Rust | <> | T: X, | | = | n/a | n/a |
| | | where | | | | |
+------------+----------+---------+--------+----------+-----------+-----------+
| Kotlin | <> | T: X | | | use, decl | inferred |
| Kotlin | <> | T: X, | | | use, decl | in, out |
| | | where | | | | |
+------------+----------+---------+--------+----------+-----------+-----------+
| Julia | {} | T <: X | X <: T | | n/a | n/a |
+------------+----------+---------+--------+----------+-----------+-----------+
| Dart | <> | extends | | | decl | in, out, |
| | | | | | | inout |
+------------+----------+---------+--------+----------+-----------+-----------+
| Go | [] | T X | | | n/a | n/a |
+------------+----------+---------+--------+----------+-----------+-----------+
| Python | [] | T: X | | | decl | inferred |
| (proposed) | | | | | | |
+------------+----------+---------+--------+----------+-----------+-----------+
Expand Down