On Sun, Apr 29, 2018 at 01:36:31PM +1000, Chris Angelico wrote: [...]
While I started off with Python 1.5, I wasn't part of the discussions about nested scopes. But I'm astonished that you say that nested scopes were controversial. *Closures* I would completely believe, but mere lexical scoping? Astonishing.
I'm not sure how you can distinguish them:
Easily. Pascal, for example, had lexical scoping back in the 1970s, but no closures. I expect Algol probably did also, even earlier. So they are certainly distinct concepts. (And yes, Pascal functions were *not* first class values.)
What you expect here is lexical scope, yes. But if you have lexical scope with no closures, the inner function can ONLY be used while its calling function is still running. What would happen if you returned 'inner' uncalled, and then called the result? How would it resolve the name 'x'?
Failing to resolve 'x' is an option. It would simply raise NameError, the same as any other name lookup that doesn't find the name. Without closures, we could say that names are looked up in the following scopes: # inner function, called from inside the creating function (1) Local to inner. (2) Local to outer (nonlocal). (3) Global (module). (4) Builtins. If you returned the inner function and called it from the outside of the factory function which created it, we could use the exact same name resolution order, except that (2) the nonlocals would be either absent or empty. Obviously that would limit the usefulness of factory functions, but since Python 1.5 didn't have closures anyway, that would have been no worse that what we had. Whether you have a strict Local-Global-Builtins scoping, or lexical scoping without closures, the effect *outside* of the factory function is the same. But at least with the lexical scoping option, inner functions can call each other while still *inside* the factory. (Another alternative would be dynamic scoping, where nonlocals becomes the environment of the caller.)
I can't even begin to imagine what lexical scope would do in the absence of closures. At least, not with first-class functions.
What they would likely do is raise NameError, of course :-) An inner function that didn't rely on its surrounding nonlocal scope wouldn't be affected. Or if you had globals that happened to match the names it was relying on, the function could still work. (Whether it would work as you expected is another question.) I expect that given the lack of closures, the best approach is to simply make sure that any attempt to refer to a nonlocal from the surrounding function outside of that function would raise NameError. All in all, closures are much better :-) -- Steve