Hi Chris,
I feel like we're pretty close to agreement. :-) The only difference is that
I still lean toward allowing one of the two left-to-right options, and not
trying to raise SyntaxErrors. I feel like detecting this kind of bad code
belongs more with a linter than the programming language itself.
But you're definitely right that it's easier to give permissions later than
take them away, and there are two natural left-to-right orders...
Speaking of implementation as Guido just raised, maybe going with what makes
the most sense in the implementation would be fitting here? I'm guessing it's
left-to-right overall (among all arguments), which is also the
simpler-to-explain rule. I would actually find it pretty weird for references
to arguments to the right to make sense even if they could...
Actually, if we use the left-to-right overall order, this is the more
conservative choice. If code worked with that order, and we later decided
that the two-pass default assignment is better, it would be
backward-compatible (except that some previously failing code would no longer
fail).
On Tue, 26 Oct 2021, Chris Angelico wrote:
Personally, I'd expect to use late-bound defaults almost all or all the time;
[...]
Interesting. In many cases, the choice will be irrelevant, and
early-bound is more efficient. There aren't many situations where
early-bind semantics are going to be essential, but there will be huge
numbers where late-bind semantics will be unnecessary.
Indeed; you could even view those cases as optimizations, and convert
late-bound immutable constants into early-bound defaults. (This optimization
would only be completely equivalent if we stick to a global left-to-right
ordering, though.)
A key difference from the PEP is that JavaScript doesn't have the notion of
"omitted arguments"; any omitted arguments are just passed in as `undefined`;
so `f()` and `f(undefined)` always behave the same (triggering default
argument behavior).
Except when it doesn't, and you have to use null instead... I have
never understood those weird inconsistencies!
Heh, yes, it can get confusing. But in my experience, all of JavaScript's
built-in features treat `undefined` as special; it's the initial value of
variables, it's the value for omitted arguments; etc. `null` is just another
sentinal value, often preferred by programmers perhaps because it's shorter
and/or better known. Also, confusingly, `undefined == null`. Eh, and `null
?? 5` acts the same as `undefined ?? 5` -- never mind. :-)
There is a subtlety mentioned in the case of JavaScript, which is that the
default value expressions are evaluated in their own scope:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters#scope_effects
Yeah, well, JS scope is a weird mess of historical artifacts.
Fortunately, we don't have to be compatible with it :)
That is true, but default values aren't part of the original history; they
were added in ECMAscript 5 in 2009. So they probably had some issues
in mind here, as it seems like added complexity, so was probably
an intentional addition.
This is perhaps worth considering for the Python context. I'm not sure this
is as important in Python, because UnboundLocalError exists (so attempts to
access things in the function's scope will fail), but perhaps I'm missing a
ramification...
Hmm. I think the only way it could possibly matter would be something like
this:
def f(x=>spam):
global spam
spam += 1
Unsure what this should do. A naive interpretation would be this:
def f(x=None):
if x is None: x = spam
global spam
spam += 1
and would bomb with SyntaxError. But perhaps it's better to permit
this, on the understanding that a global statement anywhere in a
function will apply to late-bound defaults; or alternatively, to
evaluate the arguments in a separate scope. Or, which would be a
simpler way of achieving the same thing: all name lookups inside
function defaults come from the enclosing scope unless they are other
arguments. But maybe that's unnecessarily complicated.
Inspired by your example, here's one that doesn't even involve `global`:
```
spam = 5
def f(x := spam):
spam = 10
f()
```
Does this fail (UnboundLocalError or SyntaxError or whatever) or succeed with
x set to 5? If we think of the default arguments getting evaluated in their
own scope, is its parent scope the function's scope or its enclosing scope?
The former is closer to the `if x is None` behavior we're replacing, while the
latter is a bit closer to the current semantics of default arguments.
I think this is very confusing code, so it's not particularly important to
make either choice, but we need to make a decision. The less permissive thing
seems to be using the function's scope (and fail), so perhaps that's a better
choice.
On the other hand, given that `global spam` and `nonlocal spam` would just be
preventing `spam` from being defined in the function's scope, it seems more
reasonable for your example to work, just like the following should:
```
spam = 5
def f(x := spam):
print(x, spam) # 5 5
f()
```
Here's another example where it matters whether the default expressions are
computed within their own scope:
```
def f(x := (y := 5)):
print(x) # 5
print(y) # 5???
f()
```
I feel like we don't want to allow accessing `y` in the body of `f` here,
because whether `y` is bound depends on whether `x` was passed. (If `x` is
passed, `y` won't get assigned.) This would suggest evaluating default
expressions in their own scope would be beneficial. Intuitively, the parens
are indicating a separate scope, in the same way that `(x for x in it)`
creates its own scope and thus doesn't leak `x`. On the other hand, `((y :=
x) for x in it)` does seem to leak `y`, so I'm not really sure what would be
best / most consistent here.
Erik
--
Erik Demaine | edema...@mit.edu | http://erikdemaine.org/
_______________________________________________
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/EWYHQLZOXLYH5DCZJIW3KQSSO3BV37TD/
Code of Conduct: http://python.org/psf/codeofconduct/