
On Mon, Nov 1, 2021 at 2:59 AM David Mertz, Ph.D. <david.mertz@gmail.com> wrote:
On Sun, Oct 31, 2021, 8:59 AM Chris Angelico
def foo1(a=>[1,2,3], b=>len(a)): a.append(4) print(b)
And what is the correct behaviour here?
def foo2(a=defer [1,2,3], b=defer len(a)): a.append(4) print(b)
This is a nice example. I agree they are different in the natural reading of each.
Specifically, suppose both features had been added to the language. I would expect foo1() to print "3" and foo2() to print "4".
This is also a good example of why the more general feature is BETTER. It is easy to emulate the foo1() behavior with 'defer', but impossible to emulate foo2() using '=>'.
I'd actually say that this is a good example of why the more general feature is DIFFERENT. The emulation argument is good, but we can already emulate late-binding behaviour using early-binding, and we can emulate deferred evaluation using functions, and so on; the fact that you can emulate one thing with another does not mean that it's of no value to have it. Deferred evaluation has its own set of problems, its own set of features, its own set of edge cases. I strongly encourage you to write up a detailed specification as a completely separate proposal.
E.g.
def foo3(a=defer [1,2,3], b=defer len(a)): # behaves like foo1() b = b # or eval_b = b and use new name in body a.append(4) print(b)
Note this:
def foo4(a=defer [1,2,3], b=defer len(a)) print(b) # prints 3
In order to print we actually need to walk a DAG. 'b' is an "unevaluated" object, but the interpreter would need to recognize that it depends on unevaluated 'a' ... and so on, however far up the tree it needed to walk to have only regular values (or raise a NameError maybe).
This is all precisely prior art, and is what is done by Dask Delayed: https://docs.dask.org/en/stable/delayed.html
I think it would be better as actual syntax, but generally Dask already does what I want.
The amazingly powerful thing about constructing a DAG of deferred computation is that you can find only intermediate results in a complex tree if that is all you concretely need.
I recognize that this is more complex than the niche case of late evaluation of formal parameters. But I consider that niche case trivial, and certainly not worth special syntax.
In contrast, the niche case falls out seamlessly from the more general idea.
All this is excellent and very useful, but I don't think it's the same thing as function defaults.
In terms of other prior art, deferred evaluation is the default behavior in Haskell. I admit that I find strictly functional language with no mutability a PITA. But inasmuch as Haskell has some elegance, and sometimes reasonably fast performance, it is largely because delayed evaluation is baked in.
When mutability does not exist, deferred evaluation becomes more a matter of optimization. Plus, people code to the language they're working in - if, for example, it's normal to write deeply recursive algorithms in a language that heavily optimizes recursion, that doesn't necessarily mean that it's better to write those same algorithms the same way in other languages. If both PEP 671 and some form of deferred expression were both accepted, I think their best interaction would be in introspection (help(), inspect, etc) - the descriptive part of a late-evaluated default could be reconstructed from the AST on demand. ChrisA