
On Fri, Nov 27, 2020 at 08:32:04AM +1100, Cameron Simpson wrote:
On 27Nov2020 00:25, Steven D'Aprano steve@pearwood.info wrote:
Block scoping allows shadowing within a function.
Just to this: it needn't.
Yes, I'm aware of that, and discussed languages such as Java which prohibit name shadowing within a function.
https://docs.oracle.com/javase/specs/jls/se7/html/jls-6.html#jls-6.4
Shadowing is a double-edged sword. It is sometimes useful, but often a source of hard to find bugs. One might argue that a language should allow *some* shadowing but not too much.
You could forbid shadowing of the _static_ outer scope easily enough at parse/compile time. That would prevent a certain class of easy misuse.
i = 9 { new scope here for "i" ==> parse/compile error, since "i" is in play }
Yes, that's precisely the sort of thing I discussed, and described as action-at-a-distance between two scopes, where the mere existence of a name in scope A prevents you from using the same name in scope B.
The problem is, if your inner block scope must not reuse variable names in the outer function scope, well, what's the advantage to making them seperate scopes?
Analogy: I think most of us would consider it *really weird* if this code was prohibited:
a = None
def func(): a = 1 # Local with the same name as the global prohibited.
True, it might save newbies who haven't learned about the global keyword from making a few mistakes, but that's a pretty small, and transient, benefit. (Most coders are newbies for, what, one or two percent of their active coding life?)
One possible advantage, I guess, is that if your language only runs the garbage collector when leaving a scope, adding extra scopes helps to encourage the timely collection of garbage. I don't think that's a big advantage to CPython with it's reference counting gc.
That said, there _are_ times I wish I could mark out the lifetime of a variable, akin to C level:
... i does not exist ... { int i; ... use i ... } ... i now unknown, use is an error ...
The nearest Python equivalent is:
i = blah() ... use i del i
which feels fragile - accidental assignment to "i" later is not forbidden.
Why do you care about the *name* "i" rather than whatever value is bound to that name?
I completely get the idea of caring about the lifetime of an object, e.g. I understand the need to garbage collect the exception object when leaving `except` blocks. (At least by default.)
But I don't get why I might care about the lifetime of a *name*.
try: ... except Exception as e: pass e # Name is unbound, for good reasons. e = None # But why should this be an error?
We don't generally take the position that reuse of a name in the same function is Considered Harmful, let alone *so harmful* that we need the compiler to protect us from doing so.
If I am *accidentally* reusing names, my code has much bigger problems than just the name re-use:
- my names are so generic and undescriptive that that can be re-used for unrelated purposes;
- and the function is so large and/or complicated that I don't notice when I am re-using a name.
Name re-use in the bad sense is a symptom of poor code, not a cause of it, and as such block scopes are covering up the problem.
(That's my opinionated opinion :-)