Conversation
First version of the abstract, rationale and proposal
Syntax fix in the `Rejected Ideas` subheading
|
|
||
|
|
||
| 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. |
There was a problem hiding this comment.
My experience is that type checkers ignore variance in most contexts where it does not apply, instead of reporting an error.
For example, all of the following definitions type check with mypy 0.902
def consume_one(thing: T_co) -> None: ...
def consume(things: Iterable[T_contra]) -> None: ...
Callback = Callable[[T_co], T_contra]
There was a problem hiding this comment.
I believe that (at least) mypy does not allow this
There was a problem hiding this comment.
mypy does not allow a contravariant type variable in the return type annotation, but it does allow all of the above :)
|
Hi @JelleZijlstra , it's been a while, but would you mind giving this another look? Also, do you think it a good idea for me to get started on the reference implementations at this point? |
|
Yes, I'll take another look today or tomorrow! And yes, I think a reference implementation (runtime + mypy) would be useful. |
| 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: |
There was a problem hiding this comment.
This section looks scarier than it needs to be to readers: all of these terms are standard in typing, not specific to this PEP. But this section makes it look like you need PEP-specific definitions for all these terms.
It doesn't look like the Python docs have a glossary just yet, but that could be a useful addition.
There was a problem hiding this comment.
Do you think I should remove it alltogether, or restructure (a part of) it and rename the section to e.g. "introduction", or "background"?
There was a problem hiding this comment.
I'd suggest renaming it to the bottom and removing the introductory paragraph.
| user-defined generic types. | ||
|
|
||
| type parameter | ||
| Generic types can have one or multiple type parameters. For user-defined |
There was a problem hiding this comment.
I think you can drop "For user-defined generic types". TypeVars also used for builtin generics, the TypeVars are just only in typeshed.
|
|
||
| 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 |
There was a problem hiding this comment.
I'm not sure this is true: the method signature does have something to do with the variance of the typevars, because the reason they need to be contravariant and covariant is because of the places where they appear in the signature.
There was a problem hiding this comment.
I think you're right @JelleZijlstra . I was trying to point out that associating variance with a type variable should only happen once, and that currently that isn't possible, which is pretty confusing. But I'm not sure whether it's technically incorrect to mark a typevar as variant within a method, or whether it's just redundant. What do you think? Should I leave this example out? And do you think it'd help illustrating how the current variance api is confusing by adding similar examples of how to use it incorrectly?
| class SupportsPartialOrder(typing.Protocol[-T]): | ||
| def __le__(self, other: T): ... | ||
|
|
||
| This syntax is inspired by Scala programming language [1]_. |
There was a problem hiding this comment.
| This syntax is inspired by Scala programming language [1]_. | |
| This syntax is inspired by the Scala programming language [1]_. |
You can also mention that this is already being used in the repr() of TypeVars.
|
|
||
| - it is not already co- or contravariant | ||
| - it has no type constraints | ||
| - it has no type bound |
There was a problem hiding this comment.
Variance is actually meaningful on TypeVars with a bound: https://mypy-play.net/?mypy=latest&python=3.10&gist=f72bac261a2148ca8ae6d6670dfb3459.
And similarly for value restrictions, see this example: https://mypy-play.net/?mypy=latest&python=3.10&gist=5a4f16dd13bd4b18a7c2323ba464f627. Mypy allows this but I think that's wrong; pyright gives an error on the last line. I opened python/mypy#11379.
There was a problem hiding this comment.
Variance with value restriction is counter-intuitive, and I suspect it might actually be best to forbid that.
PEP-484 uses the following example
def concat(x: AnyStr, y: AnyStr) -> AnyStr:
return x + y
class MyStr(str): ...
x = concat(MyStr('apple'), MyStr('pie'))and says that the type bound to AnyStr would be str and not MyStr. This sounds exotic and tbh as a user I would be very confused by the fact that the type variable was bound to a type different from the type of the argument.
There was a problem hiding this comment.
@JelleZijlstra I see how it's useful for covariant types to have bounds or value restrictions. But a contravariant type with bounds is strange one. In PEP484 it's explicitly called an "upper bound", but that would make bounded contravariant types useless, since they can only "go up". So without lower bounds being a thing, I think that we either
- only allow covariant typevars to have bounds/value restrictions,
- forbid bounds and restrictions on variant typevars allthogether for now,
- change the definitions so that contravariant typevars "flip" the bounds/restrictions,
- or leave it (explicitly) unspecified.
I think the last option would the cleanest, perhaps this issue will be more appropriate in e.g. your T <= int PEP @JelleZijlstra .
There was a problem hiding this comment.
I think this PEP should stay purely syntactic and not change restrictions on when covariant + bounds/constraints is allowed. That will keep it more focused. If we want changes to what kind of semantics are allowed, that can be discussed separately.
| and ``typing.ParamSpec`` instances, iff | ||
|
|
||
| - it is not already co- or contravariant | ||
| - it has no type constraints |
There was a problem hiding this comment.
The standard term is "value restriction" (https://mypy.readthedocs.io/en/stable/generics.html#type-variables-with-value-restriction)
There was a problem hiding this comment.
In PEP 484 that isn't mentioned, and refer to it as "constraints" instead. And in cpython, they are stored in TypeVar's __constraints__ attribute as a tuple. So wouldn't it make more sense to talk about "contraints" for TypeVar(..., A, B) and "bound(s)" for TypeVar(..., bound=A)?
There was a problem hiding this comment.
Interesting, I guess mypy's doocs are the odd one out there.
Co-authored-by: Jelle Zijlstra <[email protected]>
Co-authored-by: Jelle Zijlstra <[email protected]>
Co-authored-by: Jelle Zijlstra <[email protected]>
Co-authored-by: Jelle Zijlstra <[email protected]>
Co-authored-by: Jelle Zijlstra <[email protected]>
Co-authored-by: Jelle Zijlstra <[email protected]>
PR for easy feedback