-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
PEP draft for +T and -T type variable syntax #2066
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
Closed
Closed
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
527e86f
Variance operators for generic type variables: +T and -T
jorenham fdcf4d9
Syntax fix
jorenham ac952a5
enumeration indentation fix
jorenham 3cdd57e
`Specification` and `Rejected Ideas` sections
jorenham 15aedb5
Indentation fix
jorenham a717746
double backtick fixes
jorenham 4805514
Applied the suggestions of @JelleZijlstra
jorenham ad351c5
`__origin__` typo fix
jorenham 20d2dbf
Included an example of a subclass an existing generic class
jorenham e0cb451
`__origin__` typo fix (again)
jorenham afef4d7
Corrected the status to "Draft"
jorenham 4ab7c34
Merge branch 'python:master' into master
jorenham 2a6913b
Added sponsor @JelleZijstra :tada:
jorenham 525f0f3
Terminology section and allow generic base classes
jorenham 24fc299
More compact abstract and general improvements
jorenham File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
PEP: 9999 | ||
Title: Inline variance operators for generic type variables: ``+T`` and ``-T`` | ||
Author: Joren Hammudoglu <jhammudoglu at gmail.com> | ||
Sponsor: Jelle Zijlstra <jelle.zijlstra at gmail.com> | ||
Status: Draft | ||
Type: Standards Track | ||
Content-Type: text/x-rst | ||
Created: 24-Jul-2021 | ||
Post-History: 24-Jul-2021 | ||
|
||
|
||
Abstract | ||
======== | ||
|
||
This PEP proposes a consise syntax for specifying the variance of | ||
the type parameters of `user-defined generic types | ||
<https://www.python.org/dev/peps/pep-0484/#user-defined-generic-types>`_. | ||
This allow declaring a co- or contravariant type parameter with ``+T`` or | ||
``-T``, where ``T`` is a type variable. | ||
|
||
|
||
Terminology | ||
=========== | ||
|
||
This PEP uses some terms to refer to specific concepts related to Python | ||
typing, or type systems in general. For the sake of clarity, this PEP | ||
considers the following terms and definitions: | ||
|
||
type variable | ||
An instance of ``typing.TypeVar`` or ``typing.ParamSpec``, as defined | ||
in `PEP 484 <https://www.python.org/dev/peps/pep-0484/>`_ and | ||
PEP `PEP 612 <https://www.python.org/dev/peps/pep-0612/>`_. Type | ||
variables can be used as either type annotations, or to declare | ||
user-defined generic types. | ||
|
||
type parameter | ||
Generic types can have one or multiple type parameters. For user-defined | ||
generic types, these are declared using type variables. Unlike type variables, | ||
type parameters only occur in generic types. | ||
|
||
generic type | ||
A generic type, or generic class, is a class with a base class | ||
``typing.Generic``, ``typing.Protocol``, or another generic type, | ||
and has at least one type parameter. | ||
|
||
variance | ||
A type parameter of a generic type can be either covariant, contravariant, | ||
or invariant (the default). This defines the subtyping relation of the | ||
generic parameters and the generic type. See `PEP 484 | ||
<https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance>`_. | ||
|
||
variadic | ||
A variadic type parameter is either co- or contravariant. A variadic | ||
type (or variadic class) is a generic type with at least one variadic | ||
type parameter | ||
|
||
|
||
Rationale | ||
========= | ||
|
||
In PEP 484 it states: | ||
|
||
Covariance or contravariance is not a property of a type variable, | ||
but a property of a generic class defined using this variable. | ||
|
||
More specifically, variance is a property of the *type parameters* of a | ||
generic class. | ||
|
||
Currently, variadic type parameters are declared with type varianbles | ||
that are constructed with ``covariant=True`` or ``contravariant=True``, | ||
e.g.: | ||
:: | ||
|
||
from typing import TypeVar, Generic, Protocol | ||
|
||
T_co = TypeVar("T_co", covariant=True) | ||
T_contra = TypeVar("T_contra", contravariant=True) | ||
|
||
class SimpleFunction(Protocol[T_contra, T_co]): | ||
def __call__(self, x: T_contra) -> T_co: ... | ||
|
||
But type variables can also be used outside of the scope of generic | ||
types, where the concept of variance does not apply, e.g.: | ||
:: | ||
|
||
def first(things: Iterable[T]) -> T: | ||
return next(iter(things)) | ||
|
||
|
||
This example is valid, but using ``T_co`` or ``T_contra`` instead of | ||
``T`` here does not make sense, and type checkers do not allow is. | ||
|
||
In ``SimpleFunction``, ``T_co`` or ``T_contra`` are used to annotate | ||
its method as well. This is required because the variance is associated | ||
with the individual type variables. But the method signature is unrelated | ||
to the variance of the type parameters of its generic type, | ||
``SimpleFunction`` in this example. | ||
|
||
Because variance is restricted to the type parameters of generic types, | ||
it makes more sense to specify the variance of type parameters directly | ||
where the generic type is defined, removing the need to specify it on | ||
the type variables themselves. | ||
|
||
|
||
Proposal | ||
======== | ||
|
||
The variance of the type parameters of a generic class can be specified | ||
with the ``+`` and ``-`` prefix operators on their respective type | ||
variables within the type parameter list of ``typing.Generic``, | ||
``typing.Protocol``, or the generic base class. Instead of | ||
:: | ||
|
||
T_co = typing.TypeVar('T_co', covariant=True) | ||
T_contra = typing.TypeVar('T_contra', contravariant=True) | ||
|
||
class SupportsInvertOld(typing.Protocol[T_co]): | ||
def __invert__(self) -> T_co: ... | ||
|
||
class SupportsPartialOrderOld(typing.Protocol[T_contra]): | ||
def __le__(self, other: T_contra): ... | ||
|
||
|
||
The new syntax uses the ``+`` and ``-`` prefix operators to specify | ||
variadic type parameters in the same place where the generic type is | ||
declared: | ||
:: | ||
|
||
T = TypeVar("T") | ||
|
||
# covariant | ||
class SupportsInvert(typing.Protocol[+T]): | ||
def __invert__(self) -> T: ... | ||
|
||
# contravariant | ||
class SupportsPartialOrder(typing.Protocol[-T]): | ||
def __le__(self, other: T): ... | ||
|
||
This syntax is inspired by Scala programming language [1]_. | ||
|
||
|
||
|
||
Specification | ||
============= | ||
|
||
|
||
Valid use locations | ||
------------------- | ||
|
||
|
||
The new type variable variance syntax can only be used within the type | ||
parameter list of ``typing.Generic``, ``typing.Protocol``, or a generic | ||
base class. | ||
|
||
The ``+`` and ``-`` prefix operators can be used on ``typing.TypeVar`` | ||
and ``typing.ParamSpec`` instances, that are now already variadic, i.e. | ||
its ``__covariant__`` and ``__contravariant__`` attributes must be | ||
``False``. | ||
|
||
When the same type variable is used on multiple generic base classes, | ||
they must share the same variance, e.g. | ||
:: | ||
|
||
from typing import TypeVar, Callable, Container, Iterable, Protocol | ||
|
||
class LinkedList(Iterable[+T], Container[+T]): ... | ||
class EventListener(Callable[[-T], None], Protocol[-T]): ... | ||
|
||
are valid examples. | ||
|
||
|
||
Differences with current syntax | ||
------------------------------- | ||
|
||
The new typevar operators return a transparent wrapper around the | ||
original type variable, which can be accessed with the ``__origin__`` | ||
attribute on the returned wrapper. e.g.:: | ||
|
||
(+T).__origin__ is T | ||
(+T).__covariant__ is True | ||
(+T).__contravariant__ is False | ||
(+T).__name__ == T.__name__ | ||
(+T).__constraints__ == T.__constraints__ | ||
(+T).__bound__ is T.__bound__ | ||
|
||
|
||
Thus, type variables defined with ``covariant=True`` and | ||
``contravariant=True``, are not equivalent to ``+T`` and ``-T``. | ||
|
||
|
||
``+T`` and ``-T`` are not valid type annotations, and should only be | ||
used within the generic type parameter list of generic base classes, e.g.:: | ||
|
||
class Spam(typing.Generic[+KT]): ... | ||
class Eggs(typing.Protocol[-KT, +VT]): ... | ||
class HamSet(typing.Sequence[+T]): ... | ||
|
||
are valid uses. | ||
|
||
All variance rules that apply to user-defined generic types should apply | ||
in the same way with the new syntax, as they do with the current syntax, | ||
and vice-versa. | ||
|
||
|
||
|
||
Rejected Ideas | ||
============== | ||
|
||
For more detauls about discussions, see links below: | ||
|
||
- `Discussion in python/typing <https://github.com/python/typing/issues/813>`_ | ||
|
||
1. Using ``T_co = +TypeVar('T_co')`` instead of ``T_co = TypeVar('T_co', covariant=True)`` | ||
------------------------------------------------------------------------------------------ | ||
|
||
PROS: | ||
|
||
- This requires minimal changes to the syntax | ||
- Replaces the need to type ``covariant=True`` or ``contravariant=True`` | ||
with a concise operator. | ||
|
||
|
||
CONS: | ||
|
||
- The ``+`` and ``-`` copy the type variable, but type variables | ||
should be unique. | ||
- It is not obvious what to do with the name of the type variable. | ||
- Co- and contravariance are properties of the generic class, not of | ||
the individual type variables. | ||
|
||
|
||
References | ||
========== | ||
|
||
.. [1] Scala Variance | ||
https://docs.scala-lang.org/scala3/book/types-variance.html | ||
|
||
|
||
Copyright | ||
========= | ||
|
||
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive. | ||
|
||
|
||
.. | ||
Local Variables: | ||
mode: indented-text | ||
indent-tabs-mode: nil | ||
sentence-end-double-space: t | ||
fill-column: 70 | ||
coding: utf-8 | ||
End: |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/varianbles/variables/