On Sat, Nov 6, 2021 at 2:57 AM Stephen J. Turnbull <stephenjturnbull@gmail.com> wrote:
Still on the agenda as far as I can see:
1. Syntax. The proposals I can recall are a. x=>default b. *x=default c. x=@default d. maybe putting * or @ on the opposite component in b and c? e. a keyword before default such as "late" or "defer". Others? I believe Chris currently favors a.
Yes, I'm currently favouring "x=>default", though weakly; but I strongly favour syntax options that change only the part around the equals sign (no adornment before the variable name, no adornment after the expression). There are a few syntaxes listed in the PEP, and there's a plan in progress to strengthen one of those syntaxes somewhat.
2. The implementation. a. Keep an abstract representation of the default expression as a string in a dunder, and prefix the compiled body with code to evaluate it in the appropriate namespace. b. As in a, but the abstract representation is an AST or similar. c. Wrap the evaluation in a function (or function-like object) and invoke it before the compiled body (this was suggested by Steven d'Aprano as a compromise, I believe). d. Wrap the evalution in a general-purpose deferred object (this is not in the scope of PEP 671, discussion below). I believe Chris's current reference implementation is a (or if I got that wrong, closer to a than any of the others).
It would be helpful to the discussion if Chris starts by striking any of the above that he's unwilling to implement.
Sure. Let's see. a. This is what's currently implemented, plus using Ellipsis in __defaults__ as a marker that there needs to be a default expression. It's a little bit complicated, but it does mean that the vast majority of functions aren't significantly affected by this proposal. b. Less preferred than a, due to the higher cost of retaining the AST, but I'd be fine with this conceptually. c. While this is philosophically interesting, I'm not sure how it would be implemented, so I'd have to see someone else's implementation before I can truly judge it. d. Definitely not, and if someone else wants it, it can be a competing proposal. So: a and b are yes, c is dubious, d is not.
A question for Chris: In your proposal, as I understand it, an expensive default expression would always be evaluated, even if it's not always needed. Eg, in this toy example:
def foo(x:int=>expensive()): if delphic_oracle(): return x else: return 0
expensive() is always evaluated. In that (presumably quite rare) case, we'd just use a sentinel instead, of course.
It will be evaluated even if it's not referred to in the body, but only if the argument is omitted. There is a guarantee that, once the function body begins executing, all arguments (whether given values or populated from defaults) have been assigned. So, yes, if you want conditional evaluation, you do need to use a sentinel.
I have further two comments, which are mostly addressed to Steve, I guess. First, I don't really understand Steve's intended difference between 2c and 2d. Second, as I understand them, both 2c and 2d encapsulate the expression in bytecode, so the nice property of introspectability of the expression is lost. I guess you can decompile it more easily than if it's just interpolated into the function body, but if it's implemented as a closure, don't we lose the identity of the identifiers in the expression? And if it's not (eg, the function-like thing encapsulates an abstract representation of the expression rather than bytecode that computes it), what's the point of 2c? I don't see how it has any advantage over 2a or 2b.
I'm not entirely sure either. There are a few concepts that could be described, but without getting some implementation going, it'll be hard to judge them.
Footnotes: [1] Nobody's right, if everybody's wrong. -- Stephen Stills You can never have too many Steves in a discussion!
We do have a good few. Not quite as many Chrises, although I'm more likely to see Chris replying to Chris replying to Chris on a nerdy mailing list than I am anywhere else! My current implementation does have one somewhat annoying flaw: the functionality of late-bound defaults is buried in the function's bytecode, but the description of it is a function dunder, and can be changed. I'd be open to suggestions that would make this a feature of the code object instead, thus preventing desynchronization. ChrisA