Ah, sorry, still getting my head around what 'soundness' means.
Isn't the first option unsound in the same way that normal type variables are unsound, though? E.g. one could do the following:
``` T = TypeVar('T')
class Tensor(Generic[T]): def __getitem__(self, value): return 0 # Dummy value
def foo(x: Tensor): return x
x: Tensor[()] = Tensor() foo(x) ```
Mypy is fine with this, even though it wouldn't work at runtime if `Tensor` were given a complete implementation (since the argument `x` is zero-rank, so we shouldn't be able to index it).
Option one would be unsound, but at least it would be unsound in a consistent way.
On Tue, 2 Feb 2021 at 21:49, S Pradeep Kumar firstname.lastname@example.org wrote:
On Tue, Feb 2, 2021 at 1:08 PM Matthew Rahtz via Typing-sig < email@example.com> wrote:
Pradeep, do you think we should include support for unbounded tuples in the PEP? I'd prefer to hold off
I'm ok with just allowing `Tensor` to be the only unbounded variadic allowed. It would be implicitly treated as `Tensor[*Tuple[Any, ...]]`. So, we wouldn't allow explicitly using `Tensor[*Tuple[int, ...]]`.
Oh, man, super well-caught! You're right, committing to invariance by default does put us in a tricky situation.
But then - trying this with a regular `TypeVar`, mypy seems to be happy with the following:
from typing import Generic, TypeVar T = TypeVar('T') class Tensor(Generic[T]): pass def expects_arbitrary_tensor(x: Tensor): pass def expects_concrete_tensor(x: Tensor[int]): pass x: Tensor = Tensor() expects_concrete_tensor(x) y: Tensor[int] = Tensor() expects_arbitrary_tensor(y)
Any idea why that works?
A non-variadic generic class `Foo` without parameters resolves to `Foo[Any]`. As I'd mentioned, we consider `List[Any]` to be compatible with `List[int]` and vice versa, despite invariance.
To work around this, we could either
(i) allow Tuple[Any, ...] in general to be compatible with Tuple[int, str], or (ii) special-case variadic classes like Tensor so that `Tensor` is compatible with `Tensor[int, str]` and vice versa.
Both are unsound.
I'd actually be much more strongly in favour of the option where `Tuple[Any, ...]` is compatible with `Tuple[int, str]`. `TypeVar` is invariant by default too, but in order to support gradual typing doesn't it **have** to behave such that `Tuple[int]` is compatible with `Tuple[Any]`?
Could you expand on why that first option is unsound?
Both are unsound. The tuple or tensor we pass in may have zero elements
and may thus cause a runtime error. Or its element may be a type that can't be used as an `int` or `str`, which is again a runtime error.
Both are unsound for the same reasons. As I'd mentioned, we might pass an empty tuple to something that expects `Tuple[int, str]`, which would be a runtime error. For example, `x: Tuple[Any, ...] = (); foo(x)` where `def foo(x: Tuple[int, str]) -> None: x + 1`. Or it might be a tuple with a non-int class as the dimension, which again would be a runtime error if used as an `int` or a `str`. For example, `x: Tuple[Any, ...] = ("hello",); foo(x)`.
The first option is not backward-compatible because we would have to change existing errors about `Tuple[Any, ...]` not being compatible with `Tuple[int, str]`.
But, yeah, I'd appreciate opinions on the above choice.
S Pradeep Kumar