
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