
My primary reaction seems similar to Mark Shannon's. When I see this code: def is_str_list(val: List[object]) -> TypeGuard[List[str]]: ... I cannot tell what it returns. There is no readable indication in that this returns a boolean so the reader cannot immediately see how to use the function. In fact my first reaction is to assume it returns some form of List[str]. Which it most definitely does not do. Additionally, it seems like restricting this concept of a type guard to only a function that returns a bool is wrong. It is also a natural idiom to encounter functions that raise an exception if their type conditions aren't met. Those should also narrow types within a non-exception caught codepath after their return. Be simple and assume that catching any exception from their call ends their narrowing? A narrowing is meta-information about a typing side effect, it isn't a type itself. It isn't a value. So replacing a return type with it seems like too much. But if we do wind up going that route, at least make the name indicate that the return value is a bool. i.e.: def parse_num(thing: Any) -> NarrowingBool[float] The term TypeGuard is too theoretical for anyone reading code. I don't care if TypeScript uses it in their language... looking that up at a quick glance - https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-define... - it doesn't look like they ever use the term TypeGuard in the language syntax itself? good. Perhaps this would be better expressed as an annotation on the argument(s) that the function narrows? def is_str_list(val: Constrains[List[object]:List[str]) -> bool: ... I'm using Constrains as an alternative to Narrows here... still open to suggestions. But I do not think we should get hung up on TypeScript and assume that TypeGuard is somehow a good name. This particular proposed example uses : slice notation rather than a comma as a proposal that may be more readable. I'd also consider using the 'as' keyword instead of , or : but that wouldn't even parse today. *Really* what might be nice for this is our -> arrow. def assert_str_list(var: Narrows[List[object] -> List[str]]) -> None: ... as the arrow carries the implication of something effective "after" the call. I suspect adding new parsing syntax isn't what anyone had in mind for now though. : seems like a viable stopgap, whether syntax happens or not. (i'm intentionally avoiding the comma as an exploration in readability) The key point I'm trying to represent is that declaring it per argument allows for more expressive functions and doesn't interfere with their return value (if they even return anything). Clearly there's a use case that _does_ want to make the narrowing conditional on the return value boolness. I still suggest expressing that on the arguments themselves. More specific flavors of the guarding parameter such as ConstrainsWhenTrue or NarrowsWhenFalse perhaps. def is_set_of(val: Constrains[Set[Any]:Set[_T]], type: Type[_T]) -> bool: ... which might also be written more concisely as: def is_set_of(val: Set[Constrains[Any:_T]], type: Type[_T]) -> bool: If we allow the full concept. hopefully food for thought, i'm -1 right now. -gps On Fri, Feb 12, 2021 at 2:34 AM Mark Shannon <mark@hotpy.org> wrote:
Hi,
First of all, sorry for not commenting on this earlier. I only became aware of this PEP yesterday.
I like the general idea of adding a marker to show that a boolean function narrows the type of one (or more?) of its arguments. However, the suggested approach seems clunky and impairs readability.
It impairs readability, because it muddles the return type. The function in the example returns a bool. The annotation is also misleading as the annotation is on the return type, not on the parameter that is narrowed.
At a glance, most programmers should be able to work out what
def is_str_list(val: List[object]) -> bool:
returns.
But,
def is_str_list(val: List[object]) -> TypeGuard[List[str]]:
is likely to confuse and require careful reading. Type hints are for humans as well as type checkers.
Technical review. -----------------
For an annotation of this kind to be useful to a checker, that checker must perform both flow-sensitive and call-graph analysis. Therefore it is theoretically possible to remove the annotation altogether, using the following approach:
1. Scan the code looking for functions that return boolean and potentially narrow the type of their arguments. 2. Inline those functions in the analysis 3. Rely on pre-existing flow-senstive analysis to determine the correct types.
However, explicit is better and implicit. So some sort of annotation seems sensible.
I would contend that the minimal:
@narrows def is_str_list(val: List[object]) -> bool:
is sufficient for a checker, as the checker can inline anything marked @narrows. Plus, it does not mislead the reader by mangling the return type.
An alternative, and more explicit, approach would be to use variable annotations. So:
def is_str_list(val: List[object]) -> bool: """Determines whether all objects in the list are strings""" return all(isinstance(x, str) for x in val)
might become:
def is_str_list(val: List[object]) -> bool: """Determines whether all objects in the list are strings""" val: NarrowsTo[List[str]] return all(isinstance(x, str) for x in val)
Although the above lacks flow control and is thus ambiguous without the convention that `NarrowsTo` only applies if the result is True.
An alternative formulation would require the annotation to dominate the function exit:
def is_str_list(val: List[object]) -> bool: """Determines whether all objects in the list are strings""" if all(isinstance(x, str) for x in val): val: NarrowsTo[List[str]] return True return False
This is unambiguous.
Finally, I would ask for a change of name.
The "Type" part is redundant, since it is a type annotation, and the "Guard" part is incorrect. It is only a guard when used, the function itself is a predicate that narrows the type of an argument. "Narrows" or "NarrowsTo" would be better.
Cheers, Mark.
On 09/02/2021 4:21 pm, Guido van Rossum wrote:
I think we have reached consensus on PEP 647 in typing-sig. We have implementations for mypy and pyright, not sure about the rest. This PEP does not affect CPython directly except for the addition of one special item (TypeGuard) to typing.py -- it would be nice to get that in the 3.10 stdlib.
I'm CC'ing python-dev here to see if there are any further comments; if not, we can initiate the approval process by creating an issue at https://github.com/python/steering-council.
-- --Guido van Rossum (python.org/~guido <http://python.org/~guido>) /Pronouns: he/him //(why is my pronoun here?)/ < http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-c...
_______________________________________________ Python-Dev mailing list -- python-dev@python.org To unsubscribe send an email to python-dev-leave@python.org https://mail.python.org/mailman3/lists/python-dev.python.org/ Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/NOLCFYLY... Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-Dev mailing list -- python-dev@python.org To unsubscribe send an email to python-dev-leave@python.org https://mail.python.org/mailman3/lists/python-dev.python.org/ Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/4LKI6IGC... Code of Conduct: http://python.org/psf/codeofconduct/