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.

**Constructor**

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.)

**Implementation**

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 :)

**Open-endedness**

That is, should it be possible to bind `Ts` to e.g. `Tuple[int, ...]`?

Guido, you're right: Eric raised this question in the thread at https://mail.python.org/archives/list/typing-sig@python.org/thread/SQVTQYWIOI4TIO7NNBTFFWFMSMS2TA4J. I originally said no, because it seemed too complicated: a) In general, what would the result of unpacking `Ts` be? b) It introduces extra complications to the behaviour of `Union[*Ts]` - see the thread for more details.

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]:
    return args
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]: ...

x1: Tensor[Batch]
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?)

**Specific questions**

I'm also collecting everyone's responses to these at https://docs.google.com/document/d/1MhVBFRtqVSnFqpeEu9JmZSohZE1mELOw04wcObwT1LE so we have a central point of reference for all the arguments relevant to each question. I'll also clarify these in the PEP.

> 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 :)

On Tue, 26 Jan 2021 at 06:05, Guido van Rossum <guido@python.org> wrote:
(Last response for the night.)

On Mon, Jan 25, 2021 at 10:58 AM S Pradeep Kumar <gohanpra@gmail.com> wrote:
[...]
(5) What if there is an arity mismatch?

Consider the following case (the same case as Eric pointed out :) ).

```
def foo(xs: Tuple[*Ts], ys: Tuple[*Ts]) -> Tuple[*Ts]: ...

tuple_int_str: Tuple[int, str]
tuple_bool: Tuple[bool]
foo(tuple_int_str, tuple_bool)
```

We might expect this to error because the argument types have different lengths.

However, Ts = Union[Tuple[int, str], Tuple[bool]] is a valid solution, since `Tuple` is covariant.

`foo` gets treated as:

```
def foo(xs: Union[Tuple[int, str], Tuple[bool]], ys: Union[Tuple[int, str], Tuple[bool]]) -> Union[Tuple[int, str], Tuple[bool]]: ...
```

Users might be expecting this to error and might be taken aback, as I was when I tried it out.

I experimented with disallowing a variadic `Ts` from being inferred as having two different lengths and that seemed somewhat more intuitive than the above. Opinions appreciated.

The issue here is in general if you want to solve to Union or not. In mypy we generally don't, but then we end up solving to object. However here we can't solve to object (it must at least be a Tuple) so I like the error.  

# Open questions

(1) What to do about `*Tuple[Any, ...]`?

During the last tensor meeting, we discussed allowing `Tensor[Any, ...]` (and the equivalent `Tensor`) in order to aid gradual typing.

Existing code annotated as `t: Tensor` would treat `Tensor` without parameters as `Tensor[Any, ...]`. That would be a Tensor with arbitrary rank and `Any` as the dimension type. This way, changing `class Tensor` to be a variadic wouldn't immediately break existing code.

I'm yet to implement this, so I'll look into how this affects type inference.

The same goes for `*Iterable[int]`, if indeed that is feasible.

As I wrote, the default is actually as many copies of Any as are needed to make the type valid. But the `...` notation is *only* valid for Tuple, not for any other generic classes, so that syntax is not literally valid. However, I agree that this is what omitter parameters should be taken to mean.
 
(2) What to do about `*Union[...]`?

If `Ts` is a type variable bound by `tuple`, then `Ts = Union[Tuple[int, str], Tuple[bool]]` is a valid assignment. We then have to consider what unpacking that means.

TypeScript allows this:

> When the type argument for T is a union type, the union is spread over the tuple type. For example, [A, ...T, B] instantiated with X | Y | Z as the type argument for T yields a union of instantiations of [A, ...T, B] with X, Y and Z as the type argument for T respectively.

Okay, so it means `[A, ...X, B] | [A, ...Y, B] | [A, ...Z, B]`. And I guess your (slightly cryptic, or condensed) question was about the instantiation of Ts from a union of tuples. It makes sense that a distributive law applies here.

--
--Guido van Rossum (python.org/~guido)
_______________________________________________
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: mrahtz@google.com