On Thu, Dec 9, 2021 at 3:16 PM Rob Cliffe via Python-ideas <python-ideas@python.org> wrote:
Objections to PEP 671 - Summary There seems to be a problem understanding what the objections to PEP 671 are. Chris A wrote: "Part of the problem is that it is really REALLY hard to figure out what the actual objections are. I asked, and the one clear answer I got was one subjective opinion that the cognitive load exceeded the benefit. Great! That's one person's concern. I've responded to that by clarifying parts of the cognitive load problem, and that's about as far as that can go. But if there's nothing more specific than that, what do you want me to respond to? How can I address the objections if the objections are as vague as you're describing?"
Well, I have AFAIK re-read every post in the threads and am attempting to summarise the objections to PEP 671 that I have found.
Thank you. Much appreciated.
Disclaimer: I do NOT claim this is a completely objective survey. I think by now everyone knows on which side of the fence I sit. In some places I have added my own (biased) comments. Nonetheless I have honestly tried to make a fair selection from all the relevant, reasonably important posts. And if anyone thinks that their, or anyone else's objection(s) have been omitted/understated/misrepresented, they are welcome to edit this post and put the result in a new one. Disclaimer: I have not referred to Steven d'Aprano's posts, because I lack the time (ability?) to understand them all. AFAICT he is in favour of something like this PEP, but a bit different.
Unfortunately, neither do I fully understand them, so we're going to have to wait for his clarifications. Or, better, a reference implementation. Until then, let's leave that all aside.
AFAIK these were all the objections that were raised. There may be some overlap or some sub-cases but this is how I've classified them: (A) Don't like the proposed `=>` syntax. (B) Too few use cases to bother making a change to the language. Status quo wins. (C) Some form of "deferred evaluation object" would do everything this PEP does and more, and might be incompatible with it. (D) The (late-binding) default value should be a first-class object that can be accessed and manipulated. (E) Calculation of a "default value" should be done in the function body, not the function header. (F) Concerns that functions using late-bound defaults were harder to wrap. (G) Backward compatibility. (H) No other language supports both early- and late-binding.
(A) I have a few other syntaxes in the PEP, but if syntax is the only issue and I haven't listed someone's preferred syntax, I would be happy to consider others. (B) I've shown some use cases. If you think these aren't of value, no problem, but there's nothing to answer here. (C) This has been asserted without evidence repeatedly. So far, I have yet to see an example of how a deferred-evaluation object could replace default argument handling; the examples have all been massive overkill eg options for parallelism, and entail lots of extra syntax. I'm also not convinced that they'd make late-bound defaults useless, and that hasn't really been explained either. (D) Understood. My response is two-fold: firstly, no other expression in Python is a first-class object, and secondly, the manipulations possible wouldn't be sufficiently general to cover all use-cases. I do provide a textual representation of the default, which in simple situations could be eval'd; in complex situations, nothing external would work anyway. (E) That's purely a matter of opinion, but if that's the case, why aren't *all* defaults done in the body? Why do we have argument defaults in the function header at all? Surely that's of value. It certainly has been to me. (F) Also a matter of opinion, given that *a,**kw is the most common wrapping technique used, and will work reliably. Function signature algebra is a much larger challenge than this. (G) I'm not breaking compatibility in any way. (H) This is true. But if the two syntaxes can be sufficiently similar, the cost should be low, and the feature benefit would be high. Early binding lets you "def f(x=x):" in a loop and capture each x as it goes by. Late binding lets you "def f(x=>[]):" and get a new list every time. Both have their places. If Python had had late binding from the start, and no early binding, we would have developed idioms for early binding (most likely a decorator that captures values or something like that). Both behaviours are sufficiently useful that programmers WILL implement them, language support or not.
I quote from one of Chris A's replies re a `defer` keyword: 'The trouble is that this actually would be incompatible. If you can defer an expensive calculation and have some "placeholder" value in the variable 'result', then logically, you should be able to have that placeholder as a function default argument, which would be an early-bound default of the placeholder. That is quite different in behaviour from a late-bound default, so if I were to use the word "defer" for late-bound defaults, it would actually prevent the more general proposal.'
The incompatibility here, btw, would be using a "defer" keyword to mean late-binding, which isn't the same thing as a "defer expr" keyword that creates a deferred expression. PEP 671 would not be incompatible with a generic deferred-expression concept, as long as they don't use the same word.
(D) The (late-binding) default value should be a first-class object that can be accessed and manipulated.
Eric V.Smith: [AFAIU, paraphrased] I want the default value to be an object that I can inspect and change. David Mertz: "For the reasons Eric Smith and others have pointed out, I really WANT to keep inspectability of function signatures." Stephen J. Turnbull: "More than any of these issues, the lack of a well-defined, properly introspectable object bothers me."
Chris A gave a reply including 'If you consider the equivalent to be a line of code in the function body, then the signature has become MASSIVELY more useful. Instead of simply seeing "x=<object object at 0x7fba1b318690>", you can see "x=>[]" and be able to see what the value would be.'
Correct. In general, expressions are not first-class objects in Python; they only become them when turned into functions or classes (including the special functions used by genexps/comps). We do not have an introspectable, externally-testable, first-class object to represent any other expression: x = 1/y if y else "invalid" There's no object for "1/y". Trying to create one would be a nightmare of subtleties, where assignment expressions would break things, nonlocal variable references would become tricky, etc, etc. Similarly: def f(y): def g(x=1/y): ... return g There's no object for "1/y" here either. As an early-bound default, the expression is simply inlined into the body of f, as part of the construction of the function. Now with late-bound defaults:
def f(y): ... def g(x=>1/y): ... ... return g ... f(4).__defaults_extra__ ('1 / y',)
... you still don't get a first-class object for the expression, but you DO get a string constant that describes it. What is it about this third example that makes the bar so much higher than for the other two? Thank you for this summary. Are there any objections still unanswered? Which of these parts is important enough to add to the PEP? I think I'll be adding a section on introspectability, since it seems to be a hot topic, but I'm not sure about the others. ChrisA