Block-scope

I've recently been puzzled by what I considered to be a design error in Javascript. Then I realized that Python behaved the same. Finally, I realized that people were aware of the issue: these languages don't have block-scope, the only scoping construct is the function. If you declare a variable inside a loop, the same instance will be used for every iteration, and it will be visible after the end of the loop.

As I said I considered this as a design error; to me, it's not natural at all. I realize that I get this feeling because of other languages that I use, mostly OCaml but also C. The other day I was discussing with somebody, trying to convince him of the awkwardness of this, but he found that normal, said that experienced Javascript programmers know this behaviour, and that he didn't see any reason why one choice is better than the other. So I started to look for a good answer showing that one definition of scoping has much better properties than another, etc. It's not that easy.

Here are the questions that I want to share:
* Anybody knows of (a document explaining) the rationale behind JS or Python's choice?
* What solution do you prefer, and which _solid_ argument would you propose for it?

I couldn't find anything concerning the first question. I found a claim that JS2 would fix that, but it didn't seem right to me when reading the specification draft. Concerning the second question, here are possible answers...

0. Conservatism

Since C/C++/... has block-scope, it would have been wise to keep it the same, and avoid the user's puzzling. Or at least forbid the var declaration inside a block, asking the user to declare it at toplevel in a function, which is equivalent anyway.

1. Expressivity

The more properties one has to reason about a language, the better it is. One such property can be: inserting some expression (under X conditions) in a sequence doesn't change the final result of a program. It can't be true if the expression declares a variable at toplevel -- some people might advocate for the let-in construct at this point. Without block scoping it also isn't true as soon as the expression declares a variable inside a block. So the property is more constrained.

2. Static scoping

Another possibility is to ask for easier static analysis. Without block scoping, the scope gets enriched after a loop which declares a variable, *if there was at least one iteration*. This condition makes the lexical environment runtime-dependent. But well, with or without block-scope, static analysis is just impossible for dynamic languages anyway..

PS: Today I got mail on the r6rs list criticizing the absence of scope for the begin construct. It seems that even Scheme made this choice..

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

the two schemes you are

the two schemes you are comparing are instances of static and dynamic scoping.

one reason for dynamic scoping is ease of implementation. while dynamic scoping requires nothing but a stack of bound variables per identifier, static scoping can be difficult to implement in the context of higher order functions. this particular problem is called variable capture, which you will find at google...

for plain development dynamic scoping is dangerous, as it is not very transparent. it can, though, be very powerful whenever you want to implement custom language constructs (e.g. via scheme macros), where you might have to circumvent static scoping rules.

most languages with dynamic scoping also provide static scoping. everything else seems like a compromise on the implementers side to me. after all, dynamic scoping is the goto of variable bindings...

It is certainly more

It is certainly more complicated, but that kind of problem as been addressed and solved in other languages a while ago. The solution may have an impact on performance, but I don't think that the designers of JS and Python can argue that they have perf in mind that much..

i was not implying that the

i was not implying that the problem is that difficult to solve. basically the first solution, as implemented in the lambda calculus, has predated all programming languages.

still in my experience dynamic scoping does not make sense outside of the language extension domain, which is why i do suspect that pragmatism on the side of the implementation is the reason for such a choice in quite a few languages. javascript is a good example, as the language has never been intended for it's current scale. ease of implementation might have been a major factor in language design.

besides, the additional flexibility of dynamic scoping is not arguable. i prefer clean semantics where they do suffice though...

just to clear this up:

just to clear this up: Python scoping is static, not dynamic. But it's got some weird rules for some variable bindings and yes, scoping is not for any block, just functions, classes and objects.

BTW, the begin construct in Scheme is the same as (let () ...), so, if you need a new scope, just do (let ((v1 1) (v2 2) ...) ...)

you are right..

still, for the record, there should be a distinction between relaxed static scoping, and it's true form, as in lexical scoping. the former implies a mixture with dynamic scoping at some level.

Scheme's BEGIN

Just a quick remark.

BTW, the begin construct in Scheme is the same as (let () ...), so, if you need a new scope, just do (let ((v1 1) (v2 2) ...) ...)

Actually, BEGIN behaves somewhat oddly in Scheme with regard to scoping. Specifically, DEFINEs in a BEGIN are spliced into the surrounding scope. People take advantage of this when writing macros that introduce multiple new definitions of either the DEFINE or DEFINE-SYNTAX variety. (Recall that a macro expands to a single S-expression.)

oh! really news to me! so

oh! really news to me! so i believe that's how the record SRFI works, huh?

Thanks for the link.

Thanks for the link. However, it's not really a fix (if you consider the issue as a bug) since they just add a new kind of binding which is scoped correctly, but do not forbid the "broken" old style.

welcome to the ugly world of backwards-compatibility

Exercise:

  1. Release a new version of a popular, commercial web browser.
  2. In the new release, change the semantics of the web in some fundamental way--say, variable binding.
  3. Break every scripted web page in existence, including Yahoo, eBay, GMail, you name it.
  4. Try to convince users to upgrade to your new release.

Alternatively, try to convince major web application vendors to support two different semantics for your scripting language concurrently so their applications work in all popular releases of your browser. (Keep in mind it takes years before "legacy" browsers are no longer used on most desktops.)

Static scoping

Without block scoping, the scope gets enriched after a loop which declares a variable, *if there was at least one iteration*. This condition makes the lexical environment runtime-dependent.

That's not true, at least in Javascript: a declaration ("var <name>") doesn't need to be executed to introduce the variable "<name>" in the function scope.

For example, in:

var x = 0;
function f() {
  x = 1;
  if (0) { var x = 2; }
}
f();

The value of x after the call to "f" will be 0, even if "var x = 2;" is never executed. (Incidentally, I find this a little disturbing: you can't simply comment a block of code by putting an "if (0)" around it.)

About "begin" in scheme: I don't see it as much of a problem, as you can always create a new scope with "let". While it's just syntactic sugar for a call to a lambda, I think it is fair to say that it is much more convenient than the javascript equivalent:

(function (x) {
  // use locally scoped x here
)(1);

Or, a little better:

(function () {
  var x = 1;
  // use locally scoped x here
)();

PS: I'm a long time reader, but this is my first post. Back to lurking now... :-)

Ease of implementation...

I don't have any documentation, but I'd guess that ease of implementation is the motivation.

I think I would need to see some real-world bugs from competent programmers before I would classify this as an outright error as opposed to a wart.

"impossible"


But well, with or without block-scope, static analysis is just impossible for dynamic languages anyway..

Well, actually, complete static analysis is impossible for (most) currently available dynamic languages. It's certainly possible, thoug,h to statically analyze them with other degrees of completeness.

This becomes obvious when you comparing dynamic and static typing: the former starts out with basically complete flexibility while the latter is on the other end of the spectrum, but both can be moved towards each other. Of course, by definition, you cannot get static typing from dynamic or vice versa, but you can get pretty close.

Not not static

As several comments pointed out, my last point is really weak. Indeed, the not-block-scope choice doesn't break static scoping: if x is introduced in an empty iteration, it will still be accessible afterwards. It will be undefined, but no error will be thrown. That's my very problem with this question: the language already being quite weak, it is difficult to explain that some feature makes it weak.

Let's say that unfortunately, this behaviour helps programmers to forget about thinking to a correct default value.