
On Sun, Oct 31, 2021 at 2:23 PM Brendan Barnwell <brenbarn@brenbarn.net> wrote:
On 2021-10-30 19:11, Chris Angelico wrote:
I mean, here's another way to come at this. Suppose we have this under the proposal (using some random syntax picked at random):
def foo(a=1, b="two", c@=len(b), d@=a+c):
You keep saying that c and d "are argument default" just like a and b. So can you tell me what the argument default for each of those arguments?
The default for a is the integer 1. The default for b is the string "two". The default for c is the length of b. The default for d is the sum of a and c.
The default for argument a is an integer. The default for argument b is a string. Can you tell me, in comparable terms, what the defaults for arguments c and d are?
You're assuming that every default is a*default value*. That is the current situation, but it is by no means the only logical way a default can be defined. See above: c's default is the length of b, which is presumably an integer, and d's default is the sum of that and a, which is probably also an integer (unless a is passed as a float or something).
Well, at least that clarifies matters. :-)
I was already -1 on this but this moves me to a firm -100.
"The length of b" is a description in English, not any kind of programming construct. "The length of b" has no meaning in Python. What we store for the default (even if we don't want to call it a value) has to be a Python construct, not a human-language description. I could say "the default of b is the notion of human frailty poured into golden goblet", and that would be just as valid as "the length of b" as a description and just as meaningless in terms of Python's data model.
We have a very good construct to mean "the length of b". It looks like this: len(b) In CPython bytecode, it looks something like this: LOAD_GLOBAL "len" LOAD_FAST "b" CALL_FUNCTION with 1 argument (Very approximately, anyway.) And that's exactly what PEP 671 uses: in the source code, it uses len(b), and in the compiled executable, the corresponding bytecode sequence. Since we don't have a way to define human frailty and golden goblets, we don't have a good way to encode that in either source code or bytecode.
The default of every argument should be a first-class value. That's how things are now, and I think that's a very useful invariant to have. If we want to break it we need a lot more justification than "I don't like typing if x is None".
How important is this? Yes, it's the status quo, but if we evaluate every proposal by how closely it retains the status quo, nothing would ever be changed. Let's look at type checking for a moment, and compare a few similar functions: def spam(stuff: list, n: int): ... Takes a list and an integer. Easy. The integer is required. def spam(stuff: list, n: int = None): if n is None: n = len(stuff) ... Takes a list, and either an integer or None. What does None mean? Am I allowed to pass None, or is it just a construct that makes the parameter optional? _sentinel = object() def spam(stuff: list, n: int = _sentinel): if n is _sentinel: n = len(stuff) ... Takes a list, and either an integer or.... some random object. Can anyone with type checking experience (eg MyPy etc) say how this would best be annotated? Putting it like this doesn't work, nor does Optional[int]. def spam(stuff: list, n: int => len(stuff)): ... MyPy (obviously) doesn't yet support this syntax, so I can't test this, but I would assume that it would recognize that len() returns an integer, and accept this. (That's what it does if I use "= len(stuff)" and a global named stuff.) Currently, argument defaults have to be able to be evaluated at function definition time. It's fine if they can't be precomputed at compile time. Why do we have this exact restriction, neither more nor less? Is that really as important an invariant as you say?
Apart from all the other things about this proposal I don't support, I don't support the creation of a mysterious "expression" which is not a first class value and cannot be used or evaluated in any way except automatically in the context of calling a function.
You use similarly mysterious "expressions" all the time. They can't be treated as first-class values, but they can be used in the contexts they are in. This is exactly the same. You've probably even used expressions that are only evaluated conditionally: recip = 1 / x if x else 0 Is the "1 / x" a first-class value here? No - and that's why if/else is a compiler construct rather than a function. If you wouldn't use this feature, that's fine, but it shouldn't stand or fall on something that the rest of the language doesn't follow. ChrisA