Let me check whether I understand:
  1. tuple currently inherits from Sequence[T], where T is a single type.
  2. That's a problem, considering that tuple is variadic. To deal with types like Tuple[int, str], type checkers have to special-case the conversion from the e.g. two types specified to the type of the Sequence (e.g. with a Union).
  3. If we introduced variadic type variables, we could get rid of that special-casing - either because the conversion from multiple types to a single type is specified explicitly (as in Sequence[Union[*Ts]]), or because the conversion is an explicit part of the specification (as in Sequence[Ts]).
If this is right, my preference would be for making the conversion from a list of types to a single type explicit, as in Union[*Ts]. I think that preference is mostly based on the intuition that it's usually better to be explicit, but I'd be curious to hear arguments the other way.

---

Eric - to make sure we're on the same page about variadic type variables in general - I was a little confused about this:

We could extend variadic (list) type variables to accept an optional parameter called `arbitrary_len` that indicates whether a variadic type variable supports homogenous, arbitrary-length forms. By default, variadic type variables wouldn't allow this. We'd need to make it illegal to use type variables that support arbitrary lengths with an unpack operator or `Expand` because the result would be undefined.

I was thinking that variadic type variables would be arbitrary-length (and heterogeneous) by default. Is the point that unless we enforce some restrictions here, we can't properly define how to handle the conversion to a single type?

Wouldn't the following work, even without those restrictions?

Ts = TypeVar('Ts', list=True)  # Arbitrary-length, homogeneous or heterogeneous

class tuple(Generic[*Ts], Sequence[Union[*Ts]]): ...

x: tuple[int]       # Ts is bound to [int]; x is a Sequence[int]
y: tuple[int, str]  # Ts is bound to [int, str]; y is a Sequence[Union[int, str]]
z: tuple[int, ...]  # Ts is bound to [int, ...]; z is a Sequence[int]

Hmm, maybe this is equivalent to your proposal - except here, the "work" is done by the Union.

Oh, I think I see what you're saying. So variadic type variables would be arbitrary-length in that they can be bound to a fixed-size type list of any size, but they're not arbitrary length in that they can't be bound to an arbitrary-sized type list [int, ...]. Is that right?

OK, so it seems like the crux of the matter is whether [int, ...] is a valid thing for a variadic type variable to be bound to. Because in order to define tuple using a variadic type variable, [int, ...] would have to be a valid thing for a variadic type variable to be bound to.

Alright, I'll pause here to check whether I've understood so far :)

On Fri, 11 Dec 2020 at 00:24, Eric Traut <eric@traut.com> wrote:
In your example above, you asked "What is the type of b?". I agree that it should be `Union[int, str]`. That's consistent with pyright's current behavior.

Here are a few thoughts about how we could generalize variadic type variables to work with tuple.

We could extend variadic (list) type variables to accept an optional parameter called `arbitrary_len` that indicates whether variadic type variable supports homogenous, arbitrary-length forms. By default, variadic type variables wouldn't allow this. We'd need to make it illegal to use type variables that support arbitrary lengths with an unpack operator or `Expand` because the result would be undefined.

For type variables that support arbitrary length lists, we could define some simple rules for how they "collapse" to traditional (non-list) types.
1. For a variadic TypeVar that is bound to a homogenous, arbitrary-length list, the type list [T, ...] collapses to a type of T.
2. For a variadic TypeVar that is bound to a heterogenous, fixed-length list, the type list collapses to a union of the individual subtypes with literals stripped. For example, [Literal['a'], int] collapses to Union[str, int]. For an explanation of why it's important to strip literals in this case, refer to [this discussion](https://github.com/microsoft/pyright/issues/1249).

This would allow us to define the `tuple` class and its constructor as follows:
```python
_T = TypeVar("_T")
_Ts = TypeVar("_Ts", list=True, arbitrary_len=True)

class tuple(Sequence[_Ts]):
    @overload
    def __new__(cls: Type[_T], iterable: Tuple[_Ts] = ...) -> _T:
        ...

    @overload
    def __new__(cls: Type[_T], iterable: Iterable[_Ts] = ...) -> _T:
        ...
```

Thoughts?

--
Eric Traut
Contributor to pyright/pylance
_______________________________________________
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