
On Tue, Oct 26, 2021 at 3:32 AM Erik Demaine <edemaine@mit.edu> wrote:
As Jonathan Fine mentioned, if you defined the order to be a linearization of the partial order on arguments, (a) this would be complicated and (b) it would be ambiguous. I think, if you're going to forbid `def f(a := b, b:= a)` at the compiler level, you would need to forbid using late-bound arguments (at least) in least-bound argument expressions. But I don't see a reason to forbid this. It's rare that order would matter, and if it did, a quick experiment or learning "left to right" is really easy.
Oh yes, absolutely. I have never at any point considered any sort of linearization or reordering of evaluation, and it would be a nightmare. They'll always be evaluated left-to-right. The two options on the table are: 1) Allow references to any value that has been provided in any way 2) Allow references only to parameters to the left Option 2 is a simple SyntaxError on compilation (you won't even get as far as the def statement). Option 1 allows everything all up to the point where you call it, but then might raise UnboundLocalError if you refer to something that wasn't passed. The permissive option allows mutual references as long as one of the arguments is provided, but will give a peculiar error if you pass neither. I think this is bad API design. If you have a function for which one or other of two arguments must be provided, it should raise TypeError when you fail to do so, not UnboundLocalError.
Ah, but is it ALL argument defaults, or only those that are late-evaluated? Either way, it's going to be inconsistent with itself and harder to explain. That's what led me to change my mind.
I admit I missed this subtlety, though again I don't think it would often make a difference. But working out subtleties is what PEPs and discussion are for. :-)
Yeah. I have plans to try this out on someone who knows some Python but has no familiarity with this proposal, and see how he finds it.
I'd be inclined to assign the early-bound argument defaults before the late-bound arguments, because their values are already known (they're stored right in the function argument) so they can't cause side effects, and it could offer slight incremental benefits, like being able to write the following (again, somewhat artificial):
``` def manipulate(top_list): def recurse(start=0, end := len(rec_list), rec_list=top_list): ... ```
That would be the most logical semantics, if it's permitted at all.
Personally, I'd expect to use late-bound defaults almost all or all the time; they behave more how I expect and how I usually need them (I use a fair amount of `[]` and `{}` and `set()` as default values).
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.
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!
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/...
Yeah, well, JS scope is a weird mess of historical artifacts. Fortunately, we don't have to be compatible with it :)
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. ChrisA