On Tue, Oct 26, 2021 at 3:00 PM Steven D'Aprano <st...@pearwood.info> wrote:
>
> On Tue, Oct 26, 2021 at 04:48:17AM +1100, Chris Angelico wrote:
>
> > The problem is the bizarre inconsistencies that can come up, which are
> > difficult to explain unless you know exactly how everything is
> > implemented internally. What exactly is the difference between these,
> > and why should some be legal and others not?
>
> They should all be legal. Legal doesn't mean "works". Code that raises
> an exception is still legal code.

Then there's no such thing as illegal code, and my entire basis for
explanation is bunk. Come on, you know what I mean. If it causes
SyntaxError:, it's not legal code. Just because that's a catchable
exception doesn't change anything. Example:

> > def f5(x=>y + 1):
> >     global y
> >     y = 2
>

According to the previously-defined equivalencies, this would mean:

def f5(x=None):
    if x is None: x = y + 1
    global y
    y = 2

And that's a SyntaxError. Do you see what I mean now? Either these
things are not consistent with existing idioms, or they're not
consistent with each other.

Since writing that previous post, I have come to the view that
"consistency with existing idioms" is the one that gets sacrificed to
resolve this. I haven't yet gotten started on implementation
(definitely gonna get to that Real Soon Now™), but one possible
interpretation of f5, once disconnected from the None parallel, is
that omitting x would use one more than the module-level y. That
implies that a global statement *anywhere* in a function will also
apply to the function header, despite it not otherwise being legal to
refer to a name earlier in the function than the global statement.

> And lastly, f5() assigns positional arguments first (there are none),
> then keyword arguments (still none), then early-bound defaults left to
> right (none of these either), then late-bound defaults left to right
> (x=y+1) which might raise NameError if global y doesn't exist, otherwise
> it will succeed.

It's interesting that you assume this. By any definition, the header
is a reference prior to the global statement, which means the global
statement would have to be hoisted. I think that's probably the
correct behaviour, but it is a distinct change from the current
situation.

> However there is a real, and necessary, difference in behaviour which I
> think you missed:
>
>     def func(x=x, y=>x)  # or func(x=x, @y=x)
>
> The x=x parameter uses global x as the default. The y=x parameter uses
> the local x as the default. We can live with that difference. We *need*
> that difference in behaviour, otherwise these examples won't work:
>
>     def method(self, x=>self.attr)  # @x=self.attr
>
>     def bisect(a, x, lo=0, hi=>len(a))  # @hi=len(a)
>
> Without that difference in behaviour, probably fifty or eighty percent
> of the use-cases are lost. (And the ones that remain are mostly trivial
> ones of the form arg=[].) So we need this genuine inconsistency.

I agree, we do need that particular inconsistency. I want to avoid
others where possible.

> If you can live with that actual inconsistency, why are you losing sleep
> over behaviour (functions f1 through f4) which isn't actually inconsistent?

(Sleep? What is sleep? I don't lose what I don't have!)

Based on the multi-pass assignment model, which you still favour,
those WOULD be quite inconsistent, and some of them would make little
sense. It would also mean that there is a distinct semantic difference
between:

def f1(x=>y + 1, y=2): ...
def f2(x=>y + 1, y=>2): ...

in that it changes what's viable and what's not. (Since you don't like
the term "legal" here, I'll go with "viable", since a runtime
exception isn't terribly useful.) Changing the default from y=2 to
y=>2 would actually stop the example from working.

Multi-pass initialization makes sense where it's necessary. Is it
really necessary here?

> > And importantly, do Python core devs agree with less-skilled Python
> > programmers on the intuitions?
>
> We should write a list of the things that Python wouldn't have if the
> intuitions of "less-skilled Python programmers" was a neccessary
> condition.
>
> - no metaclasses, descriptors or decorators;
> - no classes, inheritence (multiple or single);
> - no slices or zero-based indexing;
> - no mutable objects;
> - no immutable objects;
> - no floats or Unicode strings;
>
> etc. I think that, *maybe*, we could have `print("Hello world")`, so
> long as the programmer's intuition is that print needs parentheses.

No, you misunderstand. I am not saying that less-skilled programmers
have to intuit things perfectly; I am saying that, when there are
drastic differences of expectation, there is probably a problem.

I can easily explain "arguments are assigned left to right". It is
much harder to explain multi-stage initialization and why different
things can be referenced.

> > Two-phase initialization is my second-best preference after rejecting
> > with SyntaxError, but I would love to see some real-world usage before
> > opening it up. Once permission is granted, it cannot be revoked, and
> > it might turn out that one of the other behaviours would have made
> > more sense.
>
> Being cautious about new syntax is often worthy, but here you are being
> overcautious. You are trying to prohibit something as a syntax error
> because it *might* fail at runtime. We don't even protect against things
> that we know *will* fail!
>
>     x = 1 + 'a'  # Not a syntax error.

But this is an error:

x = 1
def f():
    print(x)
    x = 2

And so is this:

def f(x):
    global x

As is this:

def f():
    x = 1
    global x
    x = 2

You could easily give these functions meaning using any of a variety
of rules, like "the global statement applies to what's after it" or
"the global statement applies to the whole function regardless of
placement". Why are they SyntaxErrors? Is that being overcautious, or
is it blocking code that makes no sense?

The two-pass model is closer to existing idioms. That's of value, but
it isn't the greatest justification. And given that there is no idiom
that perfectly matches the semantics, I don't consider that to be
strong enough to justify the increase in complexity.

ChrisA
_______________________________________________
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/QOSAS4KOWMB25ZCXK6GI33IG6SAGF6YW/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to