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.


> 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).

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.


> (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?

One interesting feature here is that you don't have to compile the 
default expressions into the body of the function. You can stick them in 
the code object, as distinct, introspectable thunks with a useful repr. 
Potentially, the only extra code that needs go inside the function body 
is a single byte-code to instantiate the late-bound defaults.

Even that might not need to go in the function body, it could be part of 
the CALL_FUNCTION and CALL_FUNCTION_KW op codes (or whatever we use).


> (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.

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.

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?

> 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.

Being a C programmer with a working knowledge of the CPython internals 
is not, and never has been, a prerequisite for raising ideas here.

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!

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.


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

Reply via email to