Type Variable Constraints #25
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Consider the following code:
Can foo take
(list[int], list[bool])
? Do we treat type variables within constraints as variables or just a list of possible types? This would affect how we do type checking for functions.Type variable semanticto Type Variable ConstraintsThis is forbidden in pyright but I cannot find any PEP documentation about it.
Our current implementation would accept
(list[int], list[bool])
.Personally I think to consider them as just a list of possible types is more natural to me.
@pca006132, the meaning of
TypeVar('V', int, bool)
is a typeV
which can be either unified withint
or withbool
, and with nothing else. So of course(list[int], list[bool])
shoudl be rejected. Otherwise, we could not use such variables to do things like constructor overloading.@LPTK the issue at hand is with the function
foo
, which is annotated withV1
andV2
and notV
.Unless I'm mistaken, this is basically a matter of taste and I'd vote for a list of possible types as well, e.g.(list[int], list[bool])
would be accepted.@pca006132 but if it's a list of types and not constraints, why duplicate the
V1
definition? You could just havefoo(a: V1, b: V1)
to have the same behavior if I understand it right.My understanding of Python's (admittedly murky) quantification semantics is that all type variables in a type signature are quantified in a
forall
at the top-level of the signature, in a form called "prenex polymorphism". I don't see why this should not include type variables that are reached indirectly, through type variable bounds.So, to use a clearer Haskell-inspired syntax, I understand the signature of
foo
as:A semantics where
foo
can take(list[int], list[bool])
would require a different interpretation. Either you'd say that the quantification ofV
happens at the level ofV1
andV2
's bounds, as inexists V. (V ∈ {int, bool}) => V1 ∈ {list[V], int}
, but that seems fairly complictaed. Or you could say that all variables in bound specifications are "refreshed" and are thus distinct across individual bound specifications.Both of these seem more complicated than my initial interpretation, which is also visibly the one taken by pyright. Also note that in the alternative interpretations, if you do want to share type variables between bound specifications, you're out of luck.
Yes I'm thinking if we should do this.
No, pyright would just disallow the use of type variables within the bound. I'm also thinking about this, but this would not allow us to have bounds like
list[A]
whereA
is a type variable (although this may not be very useful).Maybe we can just take the simpler approach by disallowing the use of type variables within bounds for now?
It can be pretty useful. There is no particular reason to forbid type variables in bounds. Although the use-cases are probably not very widespread, since pyright does not even support them.
Yes, for now it's certainly fine to disallow them at first.
For the record, here's one legitimately useful example of type variables in bounds. Suppose we want to write an
append
function that works on either strings or lists. Its type could be:We wouldn't want that appending two lists return a list of any type! Instead, we'd want the element types of both the argument lists and the result list to be the same. So the refreshing semantics seems pretty inadequate.