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;

See here:

https://lwn.net/Articles/847960/

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"

Correct.


> 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):
        ...

and this:

    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.

    def process(arg>=expression)

would be totally unambiguous to the intepreter, but I trust you would 
reject that because it reads like "greater than" to the human reader.


-- 
Steve
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/LMTIT4G2ASTKHZYE7HBUAYBXE7ASEOBO/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to