Too many messages indeed :)
To the extent that I get to break stalemates by being The Main Author Of The PEP:
**Packed vs unpacked**
OK, let's go with a variadic type variable referring to the packed version by default (e.g. `Ts == Tuple[T1, T2, ...]`) and using a star to unpack. Readability Counts and Explicit Is Better Than Implicit.
I *think* it should also be fine if we only allow unpacked usages (that is, all usages of variadic type variable instances must be starred), at least as of this PEP. Will update once I've tried this in the PEP.
Let's go with `TypeVarTuple`. The crux for me is still that we might want to implement special behaviour for bounds/constraints/variance later on, and using a different constructor gives us more flexibility.
(I'm happy to re-open discussion on these two things if we find important new arguments, but I also don't want us to bikeshed *too* much - so by default I'd like to consider these closed.)
Sorry, Pradeep, for forgetting to mention you'd been working on an implementation in Pyre. Absolutely cracking work - especially going above and beyond to work on it over a weekend! And you too, Eric - this is super helpful stuff, thank you! And thanks for clarifying, Rebecca :)
That is, should it be possible to bind `Ts` to e.g. `Tuple[int, ...]`?
But now I'm wondering. It seems pretty crucial that `Ts` should be bound to `Tuple[Any, ...]` when no type parameters are specified (i.e. `class Tensor(Generic[*Shape]): ...; Tensor == Tensor[Any, ...]`). There's also Guido's argument that
def foo(*args: *T) -> Tuple[*T]:
def bar(*x: *int):
y = foo(*x)
should be perfectly fine at runtime, so it would be weird if the type checker disallowed it.
Then again, in Eric's example:
def foo(a: Tuple[*Ts], b: Tuple[*Ts]): ...
foo((3, "hi"), ("hi", ))
My intuition is strongly that this should be an error. Maybe I'm saying this just because I care most about the numerical computing use-case?
def both_arguments_must_have_same_rank(x1: Tensor[*Shape], x2: Tensor[*Shape]) -> Tensor[*Shape]: ...
x2: Tensor[Time, Batch]
both_arguments_must_have_same_rank(x1, x2) # NOOOOOO
So overall I lean towards saying, no, we shouldn't allow it - `Tuple[Any, ...]` is the single exception. (Anyway, if we do find use-cases for open-endedness which are important, we can add it in a later PEP, right?)
> In the expression `x[A, *B]`, what value will be passed to the `__getitem__` method for instance `x`? Will the runtime effectively replace `*B` with `typing.Unpack[B]`?
Given that we're intending `Unpack` to be a stopgap, I'd feel uncomfortable relying on it. My first instinct would be to pass the `TypeVarTuple` instance with an attribute `B._unpacked = True`. That would preserve the most information, giving `x` access to everything inside the `TypeVarTuple`.
> Will star expressions be allowed in slice expressions?
I concur: nope.
> Am I correct in assuming that it's not OK to pass zero arguments to a `*args` parameter that has a variadic TypeVar annotation (`*args: *Ts`)?
I agree with Guido: it *should* be valid to pass zero arguments to `*args: *Ts`.
Generally, it *should* be valid for a `TypeVarTuple` to be empty: we should be able to represent rank-0 tensors (that is, a scalar, which in TensorFlow and NumPy can still be an array object), and the natural candidate is `Tensor[()]`.
> I presume that it should be an error to pass an arbitrary-length list of arguments to an `*args` parameter if it has a variadic `TypeVar` annotation?
In line with my tentative view on open-endedness above, I think that yes, this should be an error.
> PEP 484 indicates that if a type argument is omitted from a generic type, that type argument is assumed to be `Any`. What is the assumption with a variadic `TypeVar`? Should it default to `()` (empty tuple)? If we support open-ended tuples, then we could also opt for `(Any, ...)`.
As Guido and Pradeep say, I also think `(Any, ...)` is the right choice.
> What is the type of `def foo(*args: Ts) -> Union[Ts]` if `foo` is called with no arguments? In other words, what is the type of `Union[*()]`? Is it `Any`? Is this considered an error?
This should be an error, following the behaviour of `Union` at runtime (try doing `Union[()]`).
> When the constraint solver is solving for a variadic type variable, does it need to solve for the individual elements of the tuple independently? Consider, for example, `def foo(a: Tuple[Ts], b: Tuple[Ts]) -> Tuple[Ts]`. Now, let's consider the expression `foo((3, "hi"), ("hi", 5.6))`? Would this be an error? Or would you expect that the constraint solver produce an answer of `Tuple[int | str, str | float]` (or `Tuple[object, object]`)? It's much easier to implement if we can treat this as an error, but I don't know if that satisfies the use cases you have in mind.
I think this should be an error, so that my `both_arguments_must_have_same_rank` example works.
Will think about the rest tomorrow :)