I like the square-bracket variant as well, because it makes generic type definition and instantiation look the same. 

> Even if we implement an inline syntax, TypeVar should not be deprecated
> as it's still quite useful for defining re-usable type vars like
> typing.AnyStr.

This means that the new syntax will translate into a strict increase in complexity for both tooling developers and users. I think it could be worth at least considering deprecation and removal of TypeVar in a PEP, if we get to that stage.

> class C[T, S]:
>     def foo[R=int](self) -> R: pass

Could you clarify what the "=int" part stands for?

> Some ideas how we could express upper bounds, constraints, and variance: [...]

Wdyt about <= for defining an upper bound and +/- for variance annotations? IIRC both are used at least in Scala and OCaml has +/- for specifying variance and </> for polymorphic variants

---

A few more questions to consider

* How would the new syntax work for variadic generics?
* Could we make it work for ParamSpecs?
* Could we extend the syntax to type aliases which are currently only implicitly generic (when indexed with a free type variable)?

Sergei   


On Mon, Mar 14, 2022 at 2:10 PM Sebastian Rittau <srittau@rittau.biz> wrote:
After the discussion on type var defaults, Guido and I had a short
discussion about a potential inline-definition syntax for type vars.
Such a syntax has a few advantages over the definition using TypeVar:

* The type vars would be defined close to where they are actually used.
* Type vars would not leak their name into the module-global scope.
* Removes the duplication from TypeVar: _T = TypeVar("_T")
* This is in line with what other languages are doing and so more
familiar for developers coming from other languages.

Even if we implement an inline syntax, TypeVar should not be deprecated
as it's still quite useful for defining re-usable type vars like
typing.AnyStr.

Here are a few proposals we came up with.

Inline syntax using square brackets
=====================

class C[T, S]:
     def foo[R=int](self) -> R: pass

Some ideas how we could express upper bounds, constraints, and variance:

* Upper bounds: [R(Base1 | Base2)] or [R from Base1 | Base2].
* Constraints: [R as Base1 | Base2]
* Variance: [R(covariant=True)]

Advantages:

* Similar syntax to existing generics syntax.
* Used by languages such as Go and Scala.

Disadvantages:

* Square brackets are already used for indexes/slices and generics.
* We have a bit of a "bracket hell", which does not help readability:
def foo[F=dict[str, Callable[[int], object]]](): pass

Inline syntax using angle brackets
=====================

class C<T, S>:
     def foo<R>(self) -> R: pass

Advantages:

* Angle brackets are currently unused in Python and lower than/greater
than is not used within type context.
* Using angle brackets could reduce "bracket hell".
* Used by languages like C++, Java, C#, TypeScript, Delphi, so should be
fairly similar to developers coming from popular languages.

Disadvantages:

* Slightly inconsistent with existing generic syntax.

Decorators
=======

@typevar("T")
@typevar("S")
class C:
     @typevar("R", default=int)
     def foo(self) -> R: pass

Advantages:

* Doesn't require syntax changes to Python.
* Very readable (in my opinion), because it has one type var per line,
especially with more complex type vars.
* Is defined exactly like a TypeVar(), but without assigning it to a name.
* Flexible and extensible as it's using regular decorator syntax.
* Backwards compatible as it could be added to typing_extensions.

Disadvantages:

* More verbose than the other suggestions, especially when using simple
type vars.
* Non-standard syntax, compared to other languages.
* Currently requires either quoted types or "from __future__ import
annotations".

-------------------------

My personal favorite is the decorator syntax, for all the advantages
listed. The disadvantages seem minor to me or temporary. Being more
verbose makes is actually more readable. When writing TypeScript, I
often have monsters like this:

function createReactStore<
     S,
     A extends Action = AnyAction,
     E = undefined,
 >(
     someArguments: ...
) {
     // ...
}

I believe that the Python equivalent using decorator would be more readable:

@typevar("S")
@typevar("A", bound=Action, default=AnyAction)
@typevar("E", default=object)
def createReactStore(
     someArguments: ...
):
     ...

  - Sebastian

_______________________________________________
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