
On Thu, Dec 23, 2021 at 03:00:03PM -0000, asleep.cult@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