I was thinking about Joren Hammudoglu's proposed pep adding `+T`/`-T`
syntax for TypeVar variance and had a crazy idea: what if we had syntactic
support for other kinds of TypeVar customization too?
For bounds we could overload the <= operator:
from typing import TypeVar, SupportsAbs
T = TypeVar("T")
def largest_in_absolute_value(*xs: T <= SupportsAbs[float]) -> T:
return max(xs, key=abs)
And for value restrictions we could use `in`:
def concat(x: T in (str, bytes), y: T) -> T:
return x + y
(Examples taken from https://mypy.readthedocs.io/en/stable/generics.html)
The first use of the TypeVar in the function definition would have the
bound or constraints, and other uses would then follow the constraint set
in the first use. Using in or <= on the same TypeVar more than once in a
function definition is an error.
The nice thing about this syntax is that it puts all the information about
the function definition in one place. You no longer have to create a named
(and usually awkwardly named) TypeVar for each possible bound.
The syntax is reminiscent of that used in languages like Scala and
TypeScript, although in those you would write something like `def
largest_in_absolute_value
Personally, I don't find `<=` to be very readable for bounds. Would the `is` operator be better? Or perhaps the `in` operator with a single-element tuple? If there is going to be a push to add new syntax for TypeVar customizations, I'd rather see us first focus on addressing the a much bigger usability issue with type variables in Python today. Many Python users are confused by type variables because the allocation of a TypeVar is divorced from its use, and the scoping rules for a TypeVar are not obvious and difficult to understand even after reading PEP 484 and the mypy docs. The source of confusion doesn't exist in other language because type variables are defined in context where they are used, making their scopes obvious. Fixing this issue is way more important, IMO, than making it slightly easier to specify the variance of a TypeVar. I wouldn't want any of these proposed variance syntax changes to get in the way of fixing the larger issue. I remember this broader issue being raised in the typing-sig previously, but I don't recall if anyone proposed a viable solution. -Eric -- Eric Traut Contributor to pyright & pylance Microsoft
I really like the proposed syntax! Not only does it read better, but it
also makes it impossible to create a TypeVar with nonsensical combinations
of features, e.g. bound= with value restrictions. Do you think we could go
one step further and free users from the need to declare type vars?
As with the +T/-T proposal, my concern is that unless we put together a
deprecation timeline for the existing syntax, we would end up in a
situation where users have to learn both variants and tooling developers
have to do redundant work to support them.
On Sun, Oct 24, 2021 at 2:33 AM Jelle Zijlstra
I was thinking about Joren Hammudoglu's proposed pep adding `+T`/`-T` syntax for TypeVar variance and had a crazy idea: what if we had syntactic support for other kinds of TypeVar customization too?
For bounds we could overload the <= operator:
from typing import TypeVar, SupportsAbs T = TypeVar("T")
def largest_in_absolute_value(*xs: T <= SupportsAbs[float]) -> T: return max(xs, key=abs)
And for value restrictions we could use `in`:
def concat(x: T in (str, bytes), y: T) -> T: return x + y
(Examples taken from https://mypy.readthedocs.io/en/stable/generics.html)
The first use of the TypeVar in the function definition would have the bound or constraints, and other uses would then follow the constraint set in the first use. Using in or <= on the same TypeVar more than once in a function definition is an error.
The nice thing about this syntax is that it puts all the information about the function definition in one place. You no longer have to create a named (and usually awkwardly named) TypeVar for each possible bound.
The syntax is reminiscent of that used in languages like Scala and TypeScript, although in those you would write something like `def largest_in_absolute_value
(*xs: T) -> T:`, which would require new syntax in Python. I'm curious if other people think this would be a useful enhancement to the language. _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: sergei.a.lebedev@gmail.com
El dom, 24 oct 2021 a las 15:02, Sergei Lebedev (
I really like the proposed syntax! Not only does it read better, but it also makes it impossible to create a TypeVar with nonsensical combinations of features, e.g. bound= with value restrictions. Do you think we could go one step further and free users from the need to declare type vars?
That's more difficult because it requires a language-level change so you
don't just get a NameError. For example, we could introduce new syntax so
that `def concat
As with the +T/-T proposal, my concern is that unless we put together a deprecation timeline for the existing syntax, we would end up in a situation where users have to learn both variants and tooling developers have to do redundant work to support them.
I'd be OK with deprecating the current behavior once the last version that doesn't have it is EOL. For this proposal (assuming it is implemented in 3.11), that could mean we deprecate bound= after 3.10 is dead. Hopefully users will just naturally gravitate to the new syntax, just like 3.10+ code will naturally use `X | Y` instead of `Union[X, Y]`.
I like it! I remember seeing the hypothetical `<:` operator used for describing subclass relations in the Python documentation and other languages, and `<=` looks pretty similar. But since `a <= b` it often implies `a < b or a == b`, you'd also expect `<` and `==` to be valid here. It also raises the question of whether `*xs: SupportsAbs[float] >= T` is valid, since that `>=` could also imply a lower type bound, which is a thing in e.g. Scala. So what about making it look like the subclass constructor syntax instead: `*xs: T(SupportsAbs[float])` ? For value restrictions, the `in` operator can be problematic at runtime, since it always returns a boolean. So perhaps something like `x: T(str) | T(bytes)` could be used for this instead?
I second the point on confusion around TypeVar declarations and scoping
rules. I would very much like Python to have dedicated syntax for type
parameters. However, given the recent discussion on python-dev and the
position of the Steering Council on typing-only syntax, I'm not sure this
is realistic...
On Mon, Oct 25, 2021 at 5:12 AM Eric Traut
Personally, I don't find `<=` to be very readable for bounds. Would the `is` operator be better? Or perhaps the `in` operator with a single-element tuple?
If there is going to be a push to add new syntax for TypeVar customizations, I'd rather see us first focus on addressing the a much bigger usability issue with type variables in Python today. Many Python users are confused by type variables because the allocation of a TypeVar is divorced from its use, and the scoping rules for a TypeVar are not obvious and difficult to understand even after reading PEP 484 and the mypy docs. The source of confusion doesn't exist in other language because type variables are defined in context where they are used, making their scopes obvious. Fixing this issue is way more important, IMO, than making it slightly easier to specify the variance of a TypeVar. I wouldn't want any of these proposed variance syntax changes to get in the way of fixing the larger issue.
I remember this broader issue being raised in the typing-sig previously, but I don't recall if anyone proposed a viable solution.
-Eric
-- Eric Traut Contributor to pyright & pylance Microsoft _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: sergei.a.lebedev@gmail.com
Am 25.10.21 um 10:42 schrieb Sergei Lebedev:
I second the point on confusion around TypeVar declarations and scoping rules. I would very much like Python to have dedicated syntax for type parameters. However, given the recent discussion on python-dev and the position of the Steering Council on typing-only syntax, I'm not sure this is realistic...
I third the point. As far as I understand, the Steering Council is against a separate type annotation syntax, not against syntax that's only useful for type annotations. - Sebastian
Like Eric already mentioned, I think that if we are talking about making type variables more ergonomic, what would proved the biggest benefit is improving the way how type variables are declared. I don't have a strong opinion about the proposed syntax though.
I think that's unclear. In the rejection of PEP637 they stated: "The Steering Council is not particularly convinced it is of significant benefit to the static type checking language, but even if it were, at this point we’re reluctant to add general Python syntax that only (or mostly) benefits the static typing language."
Yeah, I'd brought up better TypeVar syntax at the Typing Summit [1]. At
this point, it's probably best to wait and see what the Steering Council
decides about the syntax changes for PEP 646 and for the Callable syntax
PEP before proposing new TypeVar syntax.
[1]: Slides (starting from slide 18):
https://drive.google.com/file/d/1x-qoDVY_OvLpIV1EwT7m3vm4HrgubHPG/view?usp=s...
On Mon, Oct 25, 2021 at 6:00 AM
I think that's unclear. In the rejection of PEP637 they stated: "The Steering Council is not particularly convinced it is of significant benefit to the static type checking language, but even if it were, at this point we’re reluctant to add general Python syntax that only (or mostly) benefits the static typing language." _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: gohanpra@gmail.com
-- S Pradeep Kumar
This proposal got me thinking about TypeVar(..., A, B) vs TypeVar(..., bound=Union[A, B]). From what I can tell, the only difference is that the first one accepts an instance of class C(A, B), whereas the second one does not. This appears to be analogous to OR vs XOR. See https://mypy-play.net/?mypy=latest&python=3.10&gist=7f25463261d4775b37fcbdba04796e41 With this in mind, this syntax could unify typing constraints/restrictions and upper bounds by e.g. writing TypeVar('T', A, B) as T <= A | B , and TypeVar('T', bound=Union[A, B]) as T <= A ^ B. However, the latter is a bit confusing, since Union[A, B] can be written with an A | B. But mypy does not allow it for TypeVar(..., bound=A | B), which makes sense if you consider that `class C(A | B)` and `class C(A, B)` are not the same (neither is `class C(Union[A, B])`, but that's besides the point).
That's not actually the key difference between the two forms of TypeVar.
The version with bound= can substitute any subclass of the given bound,
while the version with two or more positional type arguments can substitute
only *exactly* those types.
Example:
```
from typing import TypeVar
class C(str): ...
AnyStr = TypeVar("AnyStr", str, bytes)
def f(arg: AnyStr) -> AnyStr: ...
reveal_type(f(C())) # Line 7
T = TypeVar("T", bound=str)
def g(arg: T) -> T: ...
reveal_type(g(C())) # Line 11
```
Output:
```
main.py:7: note: Revealed type is "builtins.str*"
main.py:11: note: Revealed type is "__main__.C*"
```
On Sat, Oct 30, 2021 at 5:29 PM Joren Hammudoglu
This proposal got me thinking about TypeVar(..., A, B) vs TypeVar(..., bound=Union[A, B]). From what I can tell, the only difference is that the first one accepts an instance of class C(A, B), whereas the second one does not. This appears to be analogous to OR vs XOR. See https://mypy-play.net/?mypy=latest&python=3.10&gist=7f25463261d4775b37fcbdba04796e41
With this in mind, this syntax could unify typing constraints/restrictions and upper bounds by e.g. writing TypeVar('T', A, B) as T <= A | B , and TypeVar('T', bound=Union[A, B]) as T <= A ^ B.
However, the latter is a bit confusing, since Union[A, B] can be written with an A | B. But mypy does not allow it for TypeVar(..., bound=A | B), which makes sense if you consider that `class C(A | B)` and `class C(A, B)` are not the same (neither is `class C(Union[A, B])`, but that's besides the point). _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: guido@python.org
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-c...
Thanks for clearing that up. But this is a pretty confusing thing to me. For instance, consider this example ``` from typing import TypeVar class A: ... class B: ... class C(A): ... T = TypeVar("T", A, B) def f(x: T) -> T: return x x = C() reveal_type(x) reveal_type(f(x)) ``` Output: ``` main.py:13: note: Revealed type is "__main__.C" main.py:14: note: Revealed type is "__main__.A*" ``` Up to now, I always assumed that a type parameter simply binds to the type of its value. However, in this example, that would imply that `T != T`. This assumption is one that I think most people have, and as far as I know, this is the only exception to it.
Joren, yes this is an inconsistency. You're not alone in finding it confusing. IMO, the wording in PEP 484 doesn't make this behavior very clear. It's definitely not intuitive. However, the current behavior is well established at this point, and many type stubs and typed code bases rely on the behavior. I don't think it would be feasible to change it or remove it from the type system. I think the original justification for adding this behavior was to support use cases like this one: ``` from typing import TypeVar, Union _T1 = TypeVar("_T1", bytes, str) def add_space_constrained(val: _T1) -> _T1: if isinstance(val, str): return val + " " # No type error else: return val + b" " # No type error _T2 = TypeVar("_T2", bound=Union[bytes, str]) def add_one_bound(val: _T2) -> _T2: if isinstance(val, str): return val + " " # Type error else: return val + b" " # Type error ``` Eric Traut Contributor to Pylance & Pyright Microsoft
participants (9)
-
Alfonso L. Castaño
-
Eric Traut
-
Guido van Rossum
-
henbruas@gmail.com
-
Jelle Zijlstra
-
Joren Hammudoglu
-
S Pradeep Kumar
-
Sebastian Rittau
-
Sergei Lebedev