On 26/11/20 7:07 pm, Guido van Rossum wrote:
Hmm... In the end I think the language design issue is with functions (and how they capture variable references rather than values)
Well, every other language I know of that has a notion of closures captures variables exactly the same way that Python does. Either they're all doing it wrong, or there's something unusual about Python that makes the traditional approach inappropriate for it.
The reason I think the fault is with the for loop rather than the functions is that there are other languages, such as Scheme and Haskell, where the problem doesn't arise. The reason is that the equivalent of a for loop in those languages is some kind of mapping construct. For example the Python loop
for x in items: add_button(lambda: do_action(x))
would be written in Scheme something like
(map (lambda (x) (add_button (lambda () (do_action x)))))
Because the body is a function that gets called with the value of the loop variable, it naturally creates a new binding on each iteration, which then gets captured by the inner lambda.
having to write 'for new x in ...' is no better, because you'd still forget it.
I think it's still better (or at least more principled) than relying on default argument abuse.
BTW, an advantage of requiring 'new' is that it could be applied to other things. If it's allowed for any lvalue then you could write things like
while new x := something(): ...
Maybe we should consider introducing a new kind of function that captures values instead?
People would then have to remember to use the right kind of function.
Also I'm not sure the function is the right level of granularity. Conceivably you could want a function to capture some names as variables and others as values.
I've just been looking into the new lambda feature in C++11, and it seems they've gone down this route. You get to specify which variables from the enclosing scope get captured, and for each one, whether it's captured by reference or by value.
If we were to add something like this to Python, variable capture would have to be the default, so that existing code still works. Then we just need a way to specify that particular names are captured by value. Not sure how to do that in a way that doesn't look ugly and/or obscure.
I'm reminded of something related that was discussed a while back: a way for a function to have "constant" local names that are evaluated at definition time, like default arguments, but not part of the argument list. Kind of "default argument abuse without the abuse". Maybe we should be revisiting that idea?