I would like to be able to type a function that has multiple parameters, with the same generic variable, that only checks if the later parameters are compatible with the first.
Proposal
I propose by adding a bind_first
flag to TypeVar
:
T = TypeVar('T', bind_first=True)
def eq(x: T, y: T) -> bool: ...
eq(10, 11) # passes
eq(10, "str") # fails
This would be equivalent typing-wise to capturing the first argument in a generic class and then using that in a method, like:
T = TypeVar('T')
class eq(Generic[T]):
def __init__(self, x: T) -> None: ...
def to(self, other: T) -> bool: ...
eq(10).to(11)
eq(10).to("str")
Motiviation
There are multiple existing discussions asking for this feature, including one in the Mypy issue tracker from 2019 as well as another from the python/typing discussion board from 2022
Personally, I maintain a Python library that supports optimizing domain-specific languages, called egglog
.
In multiple places in the public API (eq
, set
, rewrite
, ne
, union
, birewrite
), I use the workaround of creating a generic class and using a method on it to support having a function with multiple arguments that need to be the same exact class.
I have received multiple times that eq(x).to(y)
is less ergonomic and intuitive than eq(x, y)
, example. For this use case, you might suggest using operator overloading to use something like __eq__
instead. In my case, I want to preserve the ability of users to define their own classes with custom equality and not use this for the meta-level eq
.
One option would be to support both forms eq(x).to(y)
(type safe) and eq(x, y)
(non type safe, but more ergonomic), but it would be unfortunate to have to force users to pick either safety or ergonomics.
Alternatives
In the issues links above, there are a few other suggestions for how to resolve this. One is very similar to this one, but suggests a same_value
flag. With that flag however, there are still some open questions about unions and subtypes. It seems like by choosing the first value passed in and binding to that, the same as if we were to make a generic class, we can have a bit clearer semantics by relying on those existing ones for the class version.
There was also a workaround involving having each sub-class you want to distinguish inherit from a different parameterized class. This works, and I would be happy using this if the only sub-classes I needed were defined in my library itself. However, in my case users can define their own sub-classes and those need to be differentiated as well, and having each have this dummy instantiated class they inherit from seems too involved.
Feedback
I would love to hear any thoughts from folks who have thought about this more, to understand if adding this kind of flag would present some unforeseen issues or if there could be a workaround with the existing typing mechanisms.