One concern I have is an inability to type narrow without creating a whole separate and fairly verbose function.

For instance, I'd often want to do an `is_str_list` check like this (rather than incur a linear cost):
```
if not val or isinstance(val[0], str):
    print(" ".join(val))
```
But to accomplish this I'd have to create another function and give it an awkward name like `is_str_list_based_on_first_element` so that it's clear that it's not safe in general.

I'm not convinced that this is much of an improvement over a possible alternative of mandating that type checkers support redefinitions with cast. The following reads really clearly to me:
```
if not val or isinstance(val[0], str):
    val = cast(List[str], val)
    print(" ".join(val))
```

If you're like me and you inline most of the time you do this kind of narrowing, this is much less invasive to get things to type check.

If we got around to adding a safe_cast a la https://github.com/python/mypy/issues/5687, this would also allow the user to explicitly opt in to strict or non strict type narrowing.

This would also easily allow narrowing of object members, as in Sebastian Kreft's QueryResult example.

Cast redefinitions feel really natural to me. I don't know what else one would want a cast redefinition to mean, so there seems little cost for type checkers to go ahead and support it, and we'd have a way to make the motivating use cases type check. Of course, we could then return to TypeGuard if we found it insufficient in practice.

On Tue, 22 Dec 2020 at 19:27, Guido van Rossum <guido@python.org> wrote:
Catching up...

On Sun, Nov 29, 2020 at 11:25 AM David Foster <davidfstr@gmail.com> wrote:
A few comments:


1. The current syntax appears to require that TypeGuard only apply to the
   *first* parameter of a function (ignoring self or cls, if present) but
   it might be valuable to apply to a different parameter in certain cases.
   For example:

    def conforms_to(type: Type[T], value: object) -> TypeGuard[value=T]: ...
                                            tentative spelling ^^^^^^

Considering prior art, TypeScript's version of TypeGuard
(a "type predicate") allows naming the parameter it applies to:

    function isNumber(x: any): x is number { ... }

I might suggest that a syntax like `TypeGuard[param=T]` be permitted to
specify a particular parameter whose type should be narrowed.

Note that keyword argument syntax can't be used in square brackets []
unless/until PEP 637 ("Support for indexing with keyword arguments") at [1]
is accepted. Also that PEP is currently slated for Python 3.11+ whereas this
PEP is currently slated for Python 3.10+.

I could potentially see breaking off the addition of keyword syntax to
TypeGuard to a later PEP, to avoid making this PEP depend on PEP 637
("Support for indexing with keyword arguments").

Heh. I talked to Anders about this and he said he couldn't recall a single example of a function where the parameter under consideration isn't the first parameter (in fact, he couldn't remember any examples where there even was another parameter). So I think this is pretty theoretical, and I would like not to complicate the proposal to handle this. **But...**

Sebastian Kreft seems to have executed a search that came to the same conclusion as Anders (typeguards are one-argument functions), but he is pushing for a way to define a typeguard as a method that guards `self`, e.g. `if query.empty(): ...`. I'm not sure how to address this without adopting David's idea except to propose that if `self` is the *only* argument the typeguard applies to `self`. But that's pretty ad-hoc and ugly. (Note that the TS equivalent would have to involve `this`, which is not explicit in TS, so apparently in TS there's no great need for this.)

So in the end David's suggestion of using PEP 637 keyword indexing could be adopted if in the future the need for this use case becomes overwhelming. (It's interesting that PEP 637 seems so popular in typing-sig -- it's needed for variadic generics as well. :-)
 

2. The discussions in the "Rejected Ideas" section of this PEP need some more
   elaboration IMHO:

2.1. §"Decorator Syntax":
> The use of a decorator was considered for defining type guards.

Here, it would be useful to show an example of what the proposed decorator
syntax was. Presumably you're referring to the syntax from PEP 586
§"Interactions with narrowing" and Ivan's comment:

    from typing import *

    @overload
    def is_int_like(x: Union[int, List[int]]) -> Literal[True]: ...
    @overload
    def is_int_like(x: object) -> bool: ...
    def is_int_like(x): ...

    def foo(scalar: Union[int, str]) -> None:
        if is_int_like(scalar):
            scalar += 3          # type of 'scalar' should be narrowed to 'int'
        else:
            scalar += "foo"      # type of 'scalar' should be narrowed to 'str'

I don't think it was that. The PEP 586 example doesn't actually work, and can't work without changing the specification of @overload.

The idea was probably something like this:
```
@typeguard(Union[int, List[int]])
def is_int_like(x: object) -> bool:
    >implementation>
```
This is inferior because it requires runtime evaluation of the type in the decorator, which would constrain forward references.

Definitely worth elaborating.
 
2.2. §"Enforcing Strict Narrowing":
> Strict type narrowing enforcement was considered, but this eliminates
> numerous valuable use cases for this functionality.

I don't actually know what this is talking about.

Presumably this is about whether the type T in TypeGuard[T] must be a subtype of the argument type. (The PEP mentions a reason why this is a bad idea: container types that are invariant.) Sebastian Kreft pointed out that allowing non-strict narrowing by default might cause false negatives for some obvious nonsense (e.g. `def f(a: int) -> TypeGuard[str]`). As a remedy, the type checker could either double check that the types have some overlap (e.g. their join isn't `object`); or (Sebastian's proposal) we could require passing an extra flag to allow non-strict narrowing. I'll let Eric choose here; I'd be okay with either (though probably the ergonomics of the implicit form are slightly better).

Again the PEP could use some more words on this topic.

Per Sebastian Kreft's suggestion, the PEP could also use some words comparing the proposal to TypeScript.

Sebastian Kreft had something about negative type guards (the problem that you can't be sure that the target is *not* of the given type if the assertion fails). I would like to give up on this and proceed as the PEP proposes.


Another Sebastian (Rittau) asked about "type assertions" which raise an exception instead of returning False. I wonder if that couldn't be handled by writing an explicit assert statement, e.g.
```
def f(x: Tuple[str, ...]):
    assert is_two_element_tuple(x)
    # Now x has type Tuple[T, T]
```
Maybe if the typeguard function raises a more user-friendly exception, it could still be declared as a TypeGuard, and the assert would just exist to help the type checker?


Conclusion: There are some loose ends, but I will sponsor this PEP and likely approve it after only minor updates. Eric, please use PEP number 647.

--
--Guido van Rossum (python.org/~guido)
_______________________________________________
Typing-sig mailing list -- typing-sig@python.org
To unsubscribe send an email to typing-sig-leave@python.org
https://mail.python.org/mailman3/lists/typing-sig.python.org/
Member address: hauntsaninja@gmail.com