-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Inline variance operators for generic type variables: +T and -T
#2045
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
12 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 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,212 @@ | ||
| PEP: 9999 | ||
| Title: Inline variance operators for generic type variables: ``+T`` and ``-T`` | ||
| Author: Joren Hammudoglu <jhammudoglu at gmail.com> | ||
| Status: Draft | ||
| Type: Standards Track | ||
| Content-Type: text/x-rst | ||
| Created: 24-Jul-2021 | ||
| Post-History: 24-Jul-2021 | ||
|
|
||
|
|
||
| Abstract | ||
| ======== | ||
|
|
||
| PEP 484 proposes a syntax for defining the `covariance and contravariance | ||
| <https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance>`_ | ||
| of `user-defined generic types | ||
| <https://www.python.org/dev/peps/pep-0484/#user-defined-generic-types>`_:: | ||
|
|
||
| T_co = typing.TypeVar('T_co', covariant=True) | ||
| T_contra = typing.TypeVar('T_contra', contravariant=True) | ||
|
|
||
|
|
||
| # a covariant generic type | ||
| class EventDispatcher(typing.Generic[T_co]): | ||
| def dispatch() -> T_co: ... | ||
|
|
||
| # a contravariant generic type | ||
| class EventListener(typing.Generic[T_contra]): | ||
| def receive(event: T_contra) -> None: ... | ||
|
|
||
|
|
||
| This PEP proposes a concise syntax for specifying the variance of | ||
| generic types by overloading the ``+`` and ``-`` prefix operators | ||
| on type variables:: | ||
|
|
||
| T = typing.TypeVar('T') | ||
|
|
||
|
|
||
| # a covariant generic type | ||
| class EventDispatcher(typing.Generic[+T]): | ||
| def dispatch() -> T: ... | ||
|
|
||
| # a contravariant generic type | ||
| class EventListener(typing.Generic[-T]): | ||
| def receive(event: T) -> None: ... | ||
|
|
||
|
|
||
|
|
||
| Rationale | ||
| ========= | ||
|
|
||
| Although defining the variance of generic type by using type variables | ||
| that are constructed with the ``covariant=True`` or ``contravariant=True`` | ||
| keyword arguments works well enough, it has downsides: | ||
|
|
||
| - 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. | ||
|
|
||
| This is why type checkers do no allow using co- or contravariant type | ||
| variables outside of generic classes. | ||
| So, instead of defining variance on the type variable itself, it | ||
| would make more sense to mark the type variable's variance when | ||
| defining the generic type, and only there. | ||
|
|
||
| - Because variance is a property of the generic type itself, type | ||
| checkers do not allow using co- or contravariant type variables | ||
| outside of generic classes. But as the variance is currently a | ||
| property of the type variables, they must be used within generic | ||
| classes. | ||
|
|
||
| - Creating separate type variables for different variances often | ||
| results in up to 3x code duplication, especially for type variables | ||
| with constraints or an upper bound. | ||
|
|
||
|
|
||
| Proposal | ||
| ======== | ||
|
|
||
|
|
||
| All of these issues can be solved by overloading the ``+`` and ``-`` | ||
| prefix operators of ``typing.TypeVar`` instances, so that ``+T`` is an | ||
| alias for a covariant type variable, and ``-T`` for a contravariant | ||
| one. These variance operators should only be used within the parameter | ||
| list of ``typing.Generic`` and ``typing.Protocol`` when subclassing them. | ||
|
|
||
| This syntax is practically identical to type parameter variance in the | ||
| Scala programming language [1]_. | ||
|
|
||
|
|
||
|
|
||
| Specification | ||
| ============= | ||
|
|
||
| The new type variable variance syntax should be used within the generic | ||
| parameter list of ``typing.Generic`` or ``typing.Protocol`` base classes, | ||
| and only there. It is not allowed to be used on type variables that | ||
| are already marked as covariant or contravariant, either with | ||
| ``covariant=True``, ``contravariant=True`` or with the new ``+T`` or | ||
| ``-T`` syntax. | ||
|
|
||
| Simplified Syntax | ||
| ----------------- | ||
|
|
||
| Instead of specifying the variance on the typevar itself:: | ||
|
|
||
| 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 | ||
| covariant and contravariant generic types inline:: | ||
|
|
||
| T = typing.TypeVar('T') | ||
|
|
||
| class SupportsInvert(typing.Protocol[+T]): | ||
| def __invert__(self) -> T: ... | ||
|
|
||
| class SupportsPartialOrder(typing.Protocol[-T]): | ||
| def __le__(self, other: T): ... | ||
|
|
||
|
|
||
| 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 ``typing.Generic`` | ||
| or ``typing.Protocol``. e.g.:: | ||
|
|
||
| class Spam(typing.Generic[+KT]): ... | ||
| class Eggs(typing.Protocol[-KT, +VT]): ... | ||
| class HamSet(typing.Sequence[T], typing.Generic[+T]): ... | ||
|
|
||
| are valid uses. | ||
|
|
||
|
|
||
| All variance rules that apply to user-defined classes 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.
Not sure this is really common. It would be useful to have examples from open source projects where you see this duplication.