On Thu, Dec 23, 2021 at 03:00:03PM -0000, asleep.c...@gmail.com wrote:

> Mark Shannon initially proposed that functions be used as types and provided 
> this example:
> 
> @Callable
> def IntToIntFunc(a:int)->int:
>      pass
> 
> def flat_map(
>      l: list[int],
>      func: IntToIntFunc
> ) -> list[int]:
>      ....

I have to admit, when Mark Shannon initially proposed that as an 
improvement over both the current and the proposed syntax, I was so 
taken aback that I initially thought he was being sarcastic and had 
to read it a second time to realise he was serious :-(

The status quo is an in-place declaration using an anonymous type:

    func: Callable[[int], list[int]]

    # proposed equivalent
    func: (int) -> list[int]

The anonymous type using Callable requires requires 26 characters, 
including 7 punctuation characters.

The PEP 677 proposal cuts that down to 18 chars (6 punctuation chars) 
while increasing readability: the arrow syntax is "executable pseudo 
code". As far back as PEP 484 in 2014 this same syntax was used in 
type-comments for Python2 straddling code:

https://www.python.org/dev/peps/pep-0484/#id50

Extending that to refer to the signature of a function is an obvious 
step. Many other languages have converged on the same, or very similar, 
syntax.

I've been using similar `param -> result` pseudo-syntax when sketching 
out code using pencil and paper, or on a whiteboard, for years, and 
nobody has failed to understand it.

In comparison, Mark's version:

    @Callable
    def IntToIntFunc(a:int)->int:
        pass

    # in the type declaration
    func: IntToIntFunc

uses 54 characters, plus spaces and newlines (including 7 punctuation 
characters); it takes up three extra lines, plus a blank line. As 
syntax goes it is double the size of Callable.

It separates the type declaration from the point at which it is used, 
potentially far away from where it is used.

It adds a new name to the global namespace, bloating the output of 
introspection tools like dir(), help() etc.

And it *requires* a named (non-anonymous) type where an anonymous type 
is all that is needed or wanted.

Being able to name types using an alias when it helps readability is 
good. Being required to name them even at the cost of hurting 
readability is not. Naming is hard, and bad names are worse than no 
names.

Consider Mark's name for the function: "IntToIntFunc", which tells us 
nothing that the signature (int)->int doesn't already tell us. It is the 
naming equivalent of the comment:

    x += 1  # Add 1 to x.

Your proposal is slightly more compact than Mark's: you drop the ending 
colon and the body ("pass"), saving one line and five characters out of 
the 54. But it suffers from the same major flaws:

- verbose and relatively heavy on vertical space;
- bloats the output of introspection tools;
- separating the definition of the type from where it is used;
- requiring a name for something which doesn't need a name.


If I had the choice between using the current syntax with Callable[] and 
the proposed PEP 677 arrow syntax, I would almost always use the arrow 
syntax. It matches the pseudo-syntax I already use when writing pseudo- 
code on paper.

If I had the choice between Callable[] and this proposed function-as-a- 
type syntax, I would stick to Callable. If I wanted to give the type a 
name, for some reason, I would still use Callable, and just write an 
alias. I cannot imagine any scenario where I would prefer this function- 
as-a-type syntax over the other two alternatives.


> I further proposed that we make the body of a function non-mandatory 
> and create a function prototype if it is omitted. I provided these 
> examples:
> 
> import typing
> 
> @typing.Callable
> def IntToIntFunc(a: int) -> int

What do you get when the inevitable occurs, and you forget the 
decorator? If I just write this:

    def IntToIntFunc(a: int) -> int

it will create what sort of object?


[...]
> This new lambda syntax also allows you to create a function prototype 
> by omitting the body. The original example can be rewritten as 
> follows:

At least that brings back the ability to write it as an anonymous type, 
but at the cost of adding a totally unnecessary keyword "lambda" and an 
unused, redundant parameter name:

    func: (int) -> int
    func: lambda (a: int) -> int


-- 
Steve
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@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/SW4NYIYWIX7VBYTGIBCTOVMDWFQSTFB4/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to