On Tue, 21 Jun 2022 at 13:17, Steven D'Aprano <steve@pearwood.info> wrote:
On Tue, Jun 21, 2022 at 12:13:08AM +1000, Chris Angelico wrote:
Nice analogy. It doesn't hold up.
Consider this function:
def f(stuff, max=>len(stuff)): stuff.append(1) print(max)
f([1,2,3])
How would you use lazy evaluation to *guarantee* the behaviour here?
By "the behaviour" I presume you want `max` evaluated before the body of the function is entered, rather than at its point of use.
Same way your implementation does: ensure that the interpreter fully evaluates `max` before entering the body of the function.
YES! Which means that.... guess what! It's NOT the same as having a default which is a deferred evaluation object! It would be *buggy behaviour* if you set the default to be a deferred evaluation object, and the interpreter evaluated it on entering the body.
The only way I can imagine doing it is basically the same as I'm doing: that late-bound argument defaults *have special syntax and meaning to the compiler*. If they were implemented with some sort of lazy evaluation object, they would need (a) access to the execution context, so you can't just use a function;
Obviously you can't just compile the default expression as a function *and do nothing else* and have late bound defaults magically appear from nowhere.
Comprehensions are implemented as functions. Inside comprehensions, the walrus operator binds to the caller's scope, not the comprehension scope.
>>> def frob(items): ... thunk = ((w:=len(items)) for x in (None,)) ... next(thunk) ... return ('w' in locals(), w) ... >>> frob([1, 2, 3, 4, 5]) (True, 5)
That seems to be exactly the behaviour needed for lazy evaluation thunks, except of course we don't need all the other goodies that generators provide (e.g. send and throw methods).
GO AND IMPLEMENT IT. I'm done arguing this. Write the code. You'll find it's a LOT more problematic than you claim.
One obvious difference is that currently if we moved that comprehension into the function signature, it would use the `items` from the surrounding scope (because of early binding). It has to be set up in such a way that items comes from the correct scope too.
If we were willing to give up fast locals, I think that the normal LEGB lookup will do the trick. That works for locals inside classes, so I expect it should work here too.
I wouldn't want to give up fast locals.
(b) guaranteed evaluation on function entry,
If that's the behaviour that people prefer, sure. Functions would need to know which parameters were:
1. defined with a lazy default; 2. and not passed an argument by the caller (i.e. actually using the default)
and for that subset of parameters, evaluate them, before entering the body of the function. That's kinda what you already do, isn't it?
But what if you wanted the default to actually be a deferred evaluation object? BAM, buggy behaviour, according to your spec.
(c) the ability to put it in the function header.
Well sure. But if we have syntax for a lazily evaluated expression it would be an expression, right? So we can put it anywhere an expression can go. Like parameter defaults in a function header.
Yes. See? You could do it as a completely separate proposal, like we've been saying.
The point is, Rob thought (and possibly still does, for all I know) that lazy evaluation is completely orthogonal to late-bound defaults. The PEP makes that claim too, even though it is not correct. With a couple of tweaks that we have to do anyway, and perhaps a change of syntax (and maybe not even that!) we can get late-bound defaults *almost* for free if we had lazy evaluation.
GO AND WRITE THE CODE.
That suggests that the amount of work to get *both* is not that much more than the work needed to get just one. Why have a car that only drives to the mall on Thursdays when you can get a car that can drive anywhere, anytime, and use it to drive to the mall on Thursday as well?
GO AND WRITE THE CODE.
Please stop arguing this point. It is a false analogy and until you can demonstrate *with code* that there is value in doing it, it is a massive red herring.
You can make further debate moot at any point by asking Python-Dev for a sponsor for your PEP as it stands right now. If you think your PEP is as strong as it can possibly be, you should do that.
(You probably want to fix the broken ReST first.)
Chris, you have been involved in the PEP process for long enough, as both a participant of discussions and writer of PEPs, that you know damn well that there is no requirement that all PEPs must have a working implementation before being accepted, let alone being *considered* by the community.
Yes, we're all very impressed that you are a competent C programmer who can write an initial implementation of your preferred design. But your repeated gate-keeping efforts to shut down debate by wrongly insisting that only a working implementation may be discussed is completely out of line, and I think you know it.
Yes, I know that a fully-working implementation isn't a prerequisite. I also know that you are arguing out of zero experience of what it actually takes to make this happen, and you have yet to overcome MANY of the problems that deferred evaluation creates. You cannot use your ignorance as an excuse to shut down other people's ideas.
I do feel some sympathy for you. I can't imagine the frustration you may be feeling -- your intent is a really tightly focused narrow feature, late-bound defaults. And not only do some people refuse to see what a fantastic idea it is, and keep raising weaknesses of the PEP itself, but some of them are more interested in a superset of the feature!
It is not a superset. I have proven this from the perspective of specification alone. You keep insisting that it is a superset. Go and write the code.
But I also can't help but feel that some of this is self-inflicted. It looks to me that you jumped the gun on not only writing a PEP but an implementation as well, *long* before there was even close to a consensus on Python-Ideas (let alone in the broader community) that late-bound defaults are worth the additional syntax, or what their precise behaviour should be. And ever since then (it seems to me) you have been trying to shut down any debate that isn't narrowly focused on your design.
Of course it is your right to code whatever you want, whenever you like. But you don't then get to complain about people wanting to consider a larger feature set when they never agreed that they wanted late-bound defaults alone in the first place.
Even if Python does later on grow a generalized lazy evaluation feature, it will only change the *implementation* of late-bound argument defaults, not their specification.
Great, we're in agreement that late-bound defaults can be implemented via lazy evaluation. You should fix the PEP that wrongly describes them as unrelated.
*facepalm* Okay, here's a compromise. Go and write a full and detailed specification of the Python-visible semantics of deferred evaluation objects, including how they would be used to implement late-bound argument defaults. Go and actually do some real work on your pet feature, instead of using the vapourware to try to shut down the one I've been working on. Go and actually do something useful instead of just arguing that "it must be possible". Once again, you're getting very very close to being killfiled. ChrisA