On Tue, Mar 4, 2014 at 10:09 PM, Steven D'Aprano email@example.com wrote:
Agreed. What I have in my head is some vague concept that the Python evaluation rules will somehow know when to evaluate the thunk and when to treat it as an object, which is (as I understand it) what happens in Algol. Again, just thinking aloud, perhaps we do this:
thunk = `some_expression` # delays evaluation a = [0, 1 + thunk] # evaluates thunk in the current scope b = [0, `1 + thunk`] # delays evaluation and creates a thunk object # equivalent to `1 + some_expression` c = b # now evaluates the thunk object d = f(2, thunk) # evaluates thunk in f's scope e = g(3, `thunk`) # passes the un-evaluated thunk object to g
Consider this just a sketch, and in no way fully thought out.
(This will most definitely need a PEP.)
PEP can come later. First, let's get some solid use-cases, and start looking at implications. The way it's described here, there's effectively magic when you try to look at an object of this type, which will break a lot of assumptions. Most people expect that:
foo = bar assert foo is bar
to be a safe assumption, but if bar is a thunk, then it's getting evaluated separately in each of those, and that's potentially going to create different objects and/or even have side effects. That's going to surprise people. On the flip side, that's something that could be dealt with with a naming convention for thunks. We have _private, __mangled, __magic__, anticollision_, CONSTANT, Class... maybe we could have thunk__ or something. It's most confusable with magic and anticollision, but since both of those are connected with specific keywords, it's reasonably likely there'll be no actual confusion.
Specific downside: There's no way to actually pass an unevaluated thunk around. Technically, `thunk__` will create a new wrapper thunk and pass that along. That'll often have the same effect, but it won't be quite identical (same as there's a difference between f(g) and f(lambda x: g(x)) in that the second one lazily looks up g), so that might cause confusion. But you could always special-case it: writing `thunk__` will be guaranteed to transmit the thunk unchanged, and if you actually mean to add another wrapper layer, use `(thunk__)` or something instead.
The biggest thing to figure out is scoping. Does a thunk take a snapshot of its enclosing scope (a closure), or is it an expression that gets evaluated in the target namespace? The latter could be extremely confusing, but the former's just what a nested function does, so this'd just be a new lambda syntax.
Python's existing lambda syntax has its flaws and its detractors, but it has one huge benefit over thunking: It exists. :) Thunking has to get over that difference. I'd like to see some proposals.