On Wed, Dec 01, 2021 at 07:07:20PM +1100, Chris Angelico wrote:
def process(func:List->int=>xs=>expression)->int: ...
I'm not sure what that's supposed to mean.
You did a pretty good job of working it out :-)
Is List->int an annotation, and if so, who's deciding on that syntax? You seem to have used two different arrows in two different ways:
Exactly my point. Beyond your arrow syntax for default parameters, and the existing return annotation use, there are two other hypothetical proposals for arrow syntax on the table:
- using `->` as an alias for typing.Callable;
- using `=>` as a more compact lambda;
I rate the first one (the typing.Callable alias) as extremely likely. It is hard to annotate parameters which are functions, and the typing community is actively working on making annotations easier. So my gut feeling is that it is only a matter of time before we have annotations like `func:List->int`. And probably closer to 3.11 than 3.20 :-)
The second, the compact lambda, I rate as only moderately likely. Guido seems to like it, but whether somebody writes a PEP and the Steering Council accepts it is uncertain. But it is something that does keep coming up, and I think we should consider it as a possibility.
Even if `=>` doesn't get used for compact lambda, it is an obvious syntax to use for *something* and so we should consider how late-binding will look, not just with existing syntax, but also with *likely* future syntax.
(When you build a road, you don't plan for just the population you have now, but also the population you are likely to have in the future.)
I don't expect anyone to take into account *arbitrary* and unpredictable future syntax, but it is wise to take into account future syntax that we're already planning or that seems likely.
List->int # presumably an annotation meaning "function that takes List, returns int"
func:ann=>dflt # late-bound default, completely unnecessary here
Come on Chris, how can you say that it is "completely unnecessary"? Unless that's an admission that late-bound defaults are all unnecessary... *wink*
There is a difference between these two:
def func(arg=lambda a: expression): ...
def func(arg=None): if arg is None: arg = lambda a: expression ...
therefore there will be a difference between:
def func(arg=lambda a: expression): def func(arg=>lambda a: expression):
If nothing else, in the first case (early binding) you get the same function object every time. In the second, you get a freshly made function object each time. Since function objects are mutable (they have a writable `__dict__` that's a visible difference even if the bodies are identical. And they may not be.
But even if it is unnecessary, it will still be permitted, just as we will be able to write:
# Now this *actually is* totally unnecessary use of late-binding def func(arg=>None):
xs=>expression # presumably a lambda function def process(args)->int # return value annotation
Why should List->int and xs=>expression use different arrows?
Because they do different things. To avoid confusion.
Chris, you wrote the PEP for the walrus operator. Why should the assignment operator use a different symbol from the assignment statement? Same reason that typing.Callable and lambda will likely use different arrows.
Anyway, if you disagree, take it up with Guido, it was his suggestion to use different arrows :-P
Wouldn't it be much more reasonable for them to use the same one, whichever that be? And if that does turn out to be "=>", then yes, I would be looking at changing PEP 671 to recommend := or =: or something, for clarity (but still an equals sign with one other symbol next to it).
Oh great, now you're going to conflict with walrus...
def process(obj:Union[T:=something, List[T]]:=func(x:=expression)+x)->T:
We ought to at least try to avoid clear and obvious conflicts between new and existing syntax.
Using `:=` is even worse than `=>`, and `=:` is just *begging* to confuse newcomers "why do we have THREE different assignment symbols?"
It's always possible to come up with pathological code. But this is only really as bad as you describe if it has zero spaces in it. Otherwise, it's pretty easy to clarify which parts go where:
def process(func: List->int => xs=>expression) -> int: ...
"My linter complains about spaces around operators! Take them out!"
Or maybe we should put more in? Spaces are optional.
def process(func : List -> int => xs => expression) -> int: ...
I'm not saying that an experienced Pythonista who is a careful reader can't work out what the meaning is. It's not *ambiguous* syntax to a careful reader. But it's *confusing* syntax to somebody who may not be as careful and experienced as you, or is coding late at night, or in a hurry, or coding while tired and emotional or distracted.
We should prefer to avoid confusable syntax when we can. The interpreter can always disambiguate assignment as an expression from assignment as a statement, but we still chose to make it easy on the human reader by using distinct symbols.
would be totally unambiguous to the intepreter, but I trust you would reject that because it reads like "greater than" to the human reader.