On Sat, Oct 30, 2021 at 03:52:14PM -0700, Brendan Barnwell wrote:

>       The way you use the term "default value" doesn't quite work for me. 
> More and more I think that part of the issue here is that "hi=>len(a)" 
> we aren't providing a default value at all.  What we're providing is 
> default *code*.  To me a "value" is something that you can assign to a 
> variable, and current (aka "early bound") argument defaults are that.

I think you are twisting the ordinary meaning of "default value" to 
breaking point in order to argue against this proposal.

When we talk about "default values" for function parameters, we always 
mean something very simple: when you call the function, you can leave 
out the argument for some parameter from your call, and it will be 
automatically be assigned a default value by the time the code in the 
body of the function runs.

It says nothing about how that default value is computed, or where it 
comes from; it says nothing about whether it is evaluated at compile 
time, or function creation time, or when the function is called.

In this case, there are at least three models for providing that default 
value:

1. The value must be something which can be computed by the compiler, at 
compile time, without access to the runtime environment. Nothing that 
cannot be evaluated by static analysis can be used as the default.

(In practice, that may limit defaults to literals.)

2. The value must be something which can be computed by the interpreter 
at function definition time. (Early binding.) This is the status quo for 
Python, but not for other languages like Smalltalk.

3. The value can be computed at function call time. (Late binding.) 
That's what Lisp (Smalltalk? others?) does. They are still default 
values, and it is specious to call them "code". From the perspective of 
the programmer, the parameter is bound to a value at call time, just 
like early binding.

Recall that the interpreter still has to execute code to get the early 
bound value. It doesn't happen by magic: the interpreter needs to run 
code to fetch the precomputed value and bind it to the parameter. Late 
binding is *exactly* the same except we leave out the "pre". The thing 
you get bound to the parameter is still a value, not the code used to 
generate that value.

The Python status quo (early binding, #2 above) is that if you want to 
delay the computation until call time, you have to use a sentinel, then 
calculate the default value yourself inside the body of the function, 
then bind it to the parameter manually. But that's just a work-around 
for lack of interpreter support for late binding.


> But these new late-bound arguments aren't really default "values", 

Of course they are, in the only sense that matters: when the body of the 
function runs, the parameter is bound to an object. That object is a 
value. How the interpreter got that value from, and when it was 
evaluated, it neither here nor there. It is still a value one way or 
another.


> they're code that is run under certain circumstances.  If we want to 
> make them values, we need to define "evaluate len(a) later" as some kind 
> of first-class value.

No we don't. Evaluating the default value later from outside the 
function's local scope will usually fail, and even if it doesn't fail, 
there's no use-cases for it. (Yet. If a good one comes up, we can add an 
API for it later.)

And evaluating it from inside the function's local scope is unnecessary, 
as the interpreter has already done it for you.

I believe that the PEP should declare that how the unevaluated defaults 
are stored prior to evaluation is a private implementation detail. We 
need an API (in the inspect module? as a method on function objects?) to 
allow consumers to query those defaults for human-readable text, e.g. 
needed by help(). But beyond that, I think they should be an opaque 
blob.

Consider the way we implement comprehensions as functions. But we don't 
make that a language rule. It's just an implementation detail, and may 
change. Likewise unevaluated defaults may be functions, or ASTs, or 
blobs of compiled byte-code, or code objects, or even just stored as 
source code. Whatever the implementors choose.


>       Increasingly it seems to me as if you are placing inordinate weight 
>       on the idea that the benefit of default arguments is providing a "human 
> readable" description in the default help() and so on.  And, to be 
> frank, I just don't care about that. 

Better help() is just the icing on the cake. The bigger advantages 
include that we can reduce the need for special sentinels, and that 
default values no longer need to be evaluated by hand in the body of the 
function, as the interpreter handles them. And we write the default 
value in the function signature, where they belong, instead of in the 
body.

Don't discount value of having the interpreter take on the grunt-work of 
evaluating defaults. Whatever extra complexity goes into the interpreter 
will be outweighed by the reduced complexity of a million functions no 
longer having to manually test for a sentinel and evaluate a default. 
Reducing grunt work is a good thing.

Remember, there are languages (Perl? bash?) where there aren't even 
parameters to functions, the interpreter merely provides you with an 
argument list and you are responsible for popping the values out of the 
list and binding them to the variables you want.

If we think that having to pop arguments from an argument list is 
unbelievably primitive, but are happy with having to check for a 
sentinel value then evaluate the actual desired default value yourself, 
then I think that we are falling for the Blub paradox.

"Late bound defaults? Who needs them? It's bloat and needless frippery."

"Default values? Who needs them? It's bloat and needless frippery."

"Named parameters? Who needs them? It's bloat and needless frippery."

"Functions? Who needs them? It's bloat and needless frippery."

I have a mate who worked for a boss who was still arguing in favour of 
unstructured code without functions or named subroutines in the late 
1990s. At the same time that they were working to fix the Y2K problem, 
his boss was telling them that GOTO was better than named functions, 
because it was no problem whatsoever to GOTO the line you wanted to jump 
to and then jump back again with another GOTO when you were finished. 
Anything more than that was just unnecessary bloat and frippery.


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

Reply via email to