Description
NOTE: The Idea described here was discussed in the context of #2 , but somehow was forgotten.
Here I would like to revive the discussion of this particular idea.
I was writing a text explaining variance of generic types and I have found something that is quite inconvenient and confusing form me in the current notations for variance. The key point is that covariance, contravariance, or invariance are the properties of generics, not of type variables. As currently agreed, only the declaration-site variance is supported. Yet, something like this is currently not prohibited by the PEP:
def fun(lst: List[T_contra]) -> T_contra:
...
It is quite unclear what that would mean. There is another problem, if one sees a type variable, then unless it has a self-explanatory name, it is not clear whether it is covariant, invariant, or contravariant, one needs to scroll up to its definition.
I would like to propose to change slightly the notation. Namely, remove covariant
and contravariant
keyword arguments from TypeVar
constructor. Instead, implement __pos__
and __neg__
methods for unary plus and minus. Pluses and minuses should be allowed only in the definition/declaration of a generic type. So that one could write
class MyClass(Generic[+T, U, -S], Mapping[T, int], Set[S]):
def my_method(self, lst: List[T]) -> S:
...
and this would mean that:
MyClass[t, u, s]
is a subtype ofMapping[t, int]
and a subtype ofSet[s]
, i.e. could be used in place of function parameters with such declared types.MyClass[t1, u, s]
is a subtype ofMyClass[t2, u, s]
ift1
is a subtype oft2
(covariance).MyClass[t, u, s2]
is a subtype ofMyClass[t, u, s1]
ifs1
is a subtype ofs2
(contravariance).MyClass
is invariant in second type argument.
Also, as discussed in #115 a shorter notation (ommiting Generic
) could be used in simple cases:
class MyClass(Mapping[+T, int]):
def my_method(self, lst: List[T]) -> int:
...
At the same time, pluses and minuses in type annotations of functions and variables should be prohibited by the PEP:
def fun(lst: List[-T]) -> -T: # Error
...
I think that the new notation would be especially clearer in situation with complex indexed generic types. One could be easily confused by
it = [] # type: Iterable[Tuple[T_contra, T_co]]
how could it be contravariant if Iterator
is defined as covariant? Such confusions will be eliminated with the new notations. Also, it would be simpler to understand for people familiar with other languages like C#, Scala or OCaml.
EDITED: there was a typo in point 1, and a mistake in the short example