I never said that there “wasn’t a case for it”. Just like you, I’m considering the case of keeping the existing semantics, but I personally think they should be removed. (More on why below.)
Maybe we should ask the PEP writer to add a migration guide? Ultimately, any flavor of type guard can be rewritten without type guards. They are typing sugar. The current type guard can always be rewritten:
def is_u(val: T) -> bool: ... # defined as before, but just return a Boolean.
and then used as follows:
def f(val: T):
if is_u(val) is not None:
u = cast(val, U)
# Use u instead of val from here on to get the `U` type you wanted.
else:
# val is unchanged, as desired.
Let’s examine the future under your proposal of keeping TypeGuard
versus the future that’s suggested by the PEP and pretend that we’re a new user who has to choose a type guard for a function that she’s writing, and ask which future is a better one to live in.
First, some background on the various type guards:
We have the current TypeGuard
:
def is_u(val: T) -> TypeGuard[U]: ...
def f(val: T):
if is_u(val):
# Type of ``val`` is narrowed to ``U``.
else:
# Type of ``val`` remains as ``T``.
Now, the PEP 724 “strict” TypeGuard
:
def is_u(val: T) -> TypeGuard[U]: ...
def f(val: T):
if is_u(val):
# Type of ``val`` is narrowed to ``T & U``.
else:
# Type of ``val`` is narrowed to ``T & Not[U]``.
Note that this is extremely logical since it can work exactly like an instance check for U
.
And the proposed LaxTypeGuard
:
def is_u(val: T) -> LaxTypeGuard[U]: ...
def f(val: T):
if is_u(val):
# Type of ``val`` is narrowed to ``T & U``. Note the difference!
else:
# Type of ``val`` remains as ``T``.
Now let’s compare the benefits for a new user in each future.
In the “stable future” that you’re proposing I guess you want to keep the current type guard, and add the strict type guard? In that case, the stable future has the following problems:
- There is doubt about which type guard is required, which requires a deep understanding of the documentation.
- Using the current type guard requires learning a new reasoning pattern that is unlike instance checks.
- The current type guard has a lot of surprising behavior based on all of the bug reports against it. In particular, it does not narrow
T
(replacing it withU
in the positive case). This will probably necessitate addingLaxTypeGuard
, and maybe deprecating it anyway.
The “progressive” future that I’m proposing would have the strict type guard only. Thus,
- There is no doubt about which type guard is required, which guides new users to the obvious choice.
- Using the type guard can work exactly like an instance check, which makes it easy to understand.
- If there’s a need, a
LaxTypeGuard
can be added. It has the benefit of mirroring the strict type guard–without the surprising behavior. It’s a bit trickier to desugar than the current type guard, so the case for adding it is stronger too.
These are two futures that I was comparing, and this is the basis for my motivation. I understand the desire to mitigate upgrade pains, but I think they’re outweighed by the benefits of creating the better future.