Re: RFC on Callable Type Syntax
On Mon, 11 Oct 2021, Guido van Rossum wrote:
I always found the following more obvious:
def data_to_table(d: Iterable[Mapping[str, float]], *, sort: bool = False, reversed: bool = False) -> Table: ...
@dataclass class Stream: converter: data_to_table | None
def add_converter(self, converter: data_to_table) -> None: self.converter = converter
Another possibility would be that functions can't be used as their types directly, but need a casting operator like so: ``` def add_converter(self, converter: typeof(data_to_table)) -> None: self.converter = converter ``` It's too bad `type` is already taken, but `typeof` is what TypeScript calls it: https://www.typescriptlang.org/docs/handbook/2/typeof-types.html -- perhaps there is a better distinguishing name, but I'll keep using `typeof` for the sake of this discussion. For static analysis, `typing.typeof` should only be used for variables that are assigned (certain) constant values and aren't re-assigned. Potentially this could be a much more general solution. Maybe interesting return values for `typeof` could be defined for `dict`s, `namedtuple`s, etc. And it could co-exist with the (P, Q) -> R proposal (which, as you probably know, is how TypeScript expresses function types).
One disadvantage of this is that now arguments HAVE TO be named which raises questions: - should they be considered at type checking time? - how to express "I don't care"?
I think it'd be natural for types constructed via `typeof` to include the argument names; I'd intend for an object "just like" this one. But you could imagine another type operator that drops details like this, e.g. `typing.forget_argument_names(typing.typeof(data_table))`. Erik -- Erik Demaine | edemaine@mit.edu | http://erikdemaine.org/
On Mon, Oct 11, 2021 at 3:22 PM Erik Demaine
On Mon, 11 Oct 2021, Guido van Rossum wrote:
No, I didn't write that, Lukasz did.
I always found the following more obvious:
def data_to_table(d: Iterable[Mapping[str, float]], *, sort: bool =
False, reversed: bool = False) -> Table:
...
-- --Guido van Rossum (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...
My apologies! Must be an issue with my email client rendering. Erik On Mon, 11 Oct 2021, Guido van Rossum wrote:
On Mon, Oct 11, 2021 at 3:22 PM Erik Demaine
wrote: On Mon, 11 Oct 2021, Guido van Rossum wrote: No, I didn't write that, Lukasz did.
> I always found the following more obvious: > > def data_to_table(d: Iterable[Mapping[str, float]], *, sort: bool = False, reversed: bool = False) -> Table: > ...
On 12 Oct 2021, at 00:09, Erik Demaine
wrote: Another possibility would be that functions can't be used as their types directly, but need a casting operator like so:
``` def add_converter(self, converter: typeof(data_to_table)) -> None: self.converter = converter ```
`type()` of any function is `types.FunctionType`. We also have `typing.Type[Class]` which fortunately since 3.9 we can spell simply as `type[Class]`, to annotate that we want the class itself, not an object of said class. I'm -1 to the idea of introducing a separate `typeof()`. The name is much too familiar to the first two to avoid confusion. Consider that a function is equivalent to an instance of a class with a `__call__()` method of the same signature: ``` def data_to_table(d: Iterable[Mapping[str, float]], *, sort: bool = False, reversed: bool = False) -> Table: ... ``` is equivalent to an object of type ``` class Converter: def __call__(self, d: Iterable[Mapping[str, float]], *, sort: bool = False, reversed: bool = False) -> Table: ... ``` In fact, Mypy already understands this equivalence: if you declare the `Converter` class I'm showing above as a Protocol, you can successfully pass `data_to_table` where a `Converter` instance is expected. Full example here: https://gist.github.com/ambv/b46d0547decf2cb0cfdf379bb5f07d50 https://gist.github.com/ambv/b46d0547decf2cb0cfdf379bb5f07d50 My proposal is to enable using a function directly in a type annotation as a shorthand for expressing such a Protocol. In other words to mean "any callable with an equivalent signature". So that this: ``` class Stream: def call_converter(self, converter: Converter) -> None: converter(self.dicts, sort=True, reversed=True) ``` would be equivalent to this: ``` class Stream: def call_converter(self, converter: data_to_table) -> None: converter(self.dicts, sort=True, reversed=True) ``` I agree that in principle there would be some additional purity in having to wrap the function annotation in some explicit type marker, like `callable[data_to_table]` (yes, that would be my suggestion instead of `typeof` since that would be specific to callables). However, in practice I think this is unnecessary boilerplate because there is no useful meaning to a `data_to_table` annotation different from `callable[data_to_table]`. So I'd skip the boilerplate. After all, the thread is about introducing a quality-of-life notation improvement. - Ł
participants (3)
-
Erik Demaine
-
Guido van Rossum
-
Łukasz Langa