
On Sun, Oct 31, 2021 at 4:55 PM <2QdxY4RzWzUUiLuE@potatochowder.com> wrote:
On 2021-10-31 at 14:56:36 +1100, Chris Angelico <rosuav@gmail.com> wrote:
On Sun, Oct 31, 2021 at 2:43 PM <2QdxY4RzWzUUiLuE@potatochowder.com> wrote:
On 2021-10-30 at 18:54:51 -0700, Brendan Barnwell <brenbarn@brenbarn.net> wrote:
On 2021-10-30 18:29, Chris Angelico wrote:
Right. That is a very real difference, which is why there is a very real difference between early-bound and late-bound defaults. But both are argument defaults.
I don't 100% agree with that.
This seems to be the crux of this whole sub-discussion. This whole thing scratches an itch I don't have, likely because of the way I learned to design interfaces on all levels. A week or so ago, I was firmly in Brendan Barnwell's camp. I really don't like how the phrase "default value" applies to PEP-671's late binding, and I'm sure that there will remain cases in which actual code inside the function will be required. But I'm beginning to see the logic behind the arguments (pun intended) for the PEP.
Current versions of the PEP do not use the term "default value" when referring to late binding (or at least, if I've made a mistake there, then please point it out so I can fix it). I'm using the term "default expression", or just "default" (to cover both values and expressions).
I still see anything more complicated than a constant or an extremely simple expression (len(a)? well, ok, maybe; (len(a) if is_prime((len(a)) else next_larger_prime(len(a)))? don't push it) as no longer being a default, but something more serious, but I don't have a better name for it than "computation" or "part of the function" or even "business logic" or "a bad API."
If your function header doesn't fit in the source code for your function header, then you're probably doing things that are too complicated :) Nothing's changing there.
And yes; there will always be cases where you can't define the default with a simple expression. For instance, a one-arg lookup might raise an exception where a two-arg one could return a default value ...
Am I getting ahead of myself, or veering into the weeds, if I ask whether you can catch the exception or what the stacktrace might show?
(At this point, that's probably more of a rhetorical question. Again, this is an itch I don't have, so I probably won't use it much.)
Ah, sorry, I wasn't too clear here. Consider this API: _sentinel = object() def get_thing(name, default=_sentinel): populate_thing_cache(name) if name in thing_cache: return thing_cache[name] if default is not _sentinel: return default raise ThingNotFoundError In this case, there's no late-bound default value that could be used here. The code to test whether we got one argument or two has to happen down below. So this sort of code wouldn't change as a result of PEP 671; it still needs to be able to be called with either one argument or two, and the distinction can't be written as "if default is _sentinel: default = ..." at the top of the function. But in terms of catching exceptions from default expressions: No, you can't, unless you wrap it in a function or something. I don't expect this sort of thing to be a common need, and if it is, the exception-catching part probably needs its own descriptive name.
The human description language/wording is different; , but what Python spells "default value," Common Lisp spells "initform." Python is currently much less flexible about when and in what context default values are evaluated; PEP-671 attempts to close that gap, but is hampered by certain technical and emotional baggage.
Lisp's execution model is quite different from Python's, but I'd be curious to hear more about this. Can you elaborate?
https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node64.html explains it in great detail with examples; you're interested in &optional, initform, and possibly supplied-p. Summarizing what I think is relevant:
(lambda (&optional x)) is a function with an optional parameter called x. Calling that function without a parameter results in x being bound to nil (Lisp's [overloaded] canonical "false"/undefined/null value) in the function body.
(lambda (&optional (x 4))) is a function with an optional parameter called x with an initform. Calling that function without a parameter results in x being bound to the value 4 in the function body. Calling that function with a parameter results in x being bound to the value of that parameter.
(lambda (&optional (x 4 p))) is a function with an optional parameter called x with an initform and a supplied-p parameter called p. Calling that function without a parameter results in p being bound to nil and x being bound to the value 4 in the function body. Calling that function with a parameter results in p being bound to t (Lisp's canonical "true" value) and x being bound to the value of that parameter.
Ah okay. So what this gives you is a very clear indication of whether the default value was used or not. I'm not officially recommending this.... actually, let's make that stronger: This is bad code, but it's possible...
def foo(n=>(n_unset := True) and 123): ... try: n_unset ... except UnboundLocalError: print("n was set to", n) ... print("The value of n is:", n) ... foo() The value of n is: 123 foo(123) n was set to 123 The value of n is: 123
Don't. Just don't. :) ChrisA