On one hand, it's true that Scheme has a lot of things JS doesn't, and vice versa; and the feel of the languages is very different. On the other hand, it's completely clueless to claim JS doesn't have lexical scoping. Only someone who didn't understand what the alternatives to lexical scoping were could claim that.
Quite the contrary. Javascript has a strange, ugly hybrid of lexical and function scope that is not that of Scheme, which I suspect that Bob understands very well given his interest in languages (and that he works on a language that compiles to JS).
This is not lexical scoping:
function example(k) {
function ugly() {return i};
for (var i = 0; i < k; i++) {}
return ugly;
}
example(4)() // => 4
Yes, the scoping in your example is lexical. That's why ugly returns k regardless of where you call it from; we can reason lexically to show that.
The i in ugly is lexically resolved to refer to a particular declaration, the one on the line below it. Scheme's lexical scoping rules are not the only possible lexical scoping rules. A lexical scoping system does not cease to be lexical simply because it is not the same as Scheme's lexical scoping. For it to be non-lexical, the scoping would have to depend on something other than the lexical structure of the program. For example, you could just as well have a dynamically-scoped JS in which ugly's return value would depend on its caller's binding for i, and in which calling a function with a free i from within example would make example's binding for i visible to that other function, but that isn't the way JS works.
It's quite reasonable to argue about whether that scoping is sane, and indeed with "let", the ES folks are giving us a saner binding construct; but it is certainly the case that the relationship between the declaration and the use of a variable is established by the lexical structure of the program text, not the dynamic structure of the executing program.
The "WAT" for me is that the `var i` is hoisted _out_ of the `for`, which IMHO, breaks the principle of least astonishment. I would expect the `i` to be lexically scoped to the body of the `for` block, instead of being hoisted out into the parent scope of the `for` block.
I speculate that this is because Javascript takes stuff from Self where loops are functions.
So probably Javascript was meant to write something like:
while(function(k) { ++i; return i < k; });
where the while function would call the closure until it got false at which point the while function itself would return
so having var declarations be hoisted was not a problem in this syntax because everything (including if and while) would have its own scope due to being a function
In the post, I specifically said Scheme had lexical block scoping. So, even if JavaScript had full lexical scoping (which it does not, thanks to with() and the global object), it still wouldn't have block scope (thanks to hoisting, though addressed in ES6), which I think is a defining feature of Scheme's approach to scope.
You're right about with(), which I'd forgotten about — JS with with() is not lexically scoped!
Access to the global object is still statically decidable. It just might raise an exception. This is not really different from Scheme.
I agree that block scope is a defining feature of Scheme's (and ALGOL's, and C's) approach to scope. However, Scheme's block scopes are all functions, at least if you accept the macro-definition of let that's been there since R5RS, rather than treating let as a separate primitive construct.
I would argue that Javascript is not lexically scoped because the scope of a variable definition does not depend on the lexical structure of a program (with the sole exception of function boundaries). This is true even to the point of multiple definitions having no effect on the scope of a variable.
I think that what you are arguing is that Javascript is statically scoped.
Edit: I think there is some confusion here about scoping. One axis is dynamic versus lexical scoping (are there other options?). Another axis is function, block, global, etc. scope. They seem orthogonal to me.
This is not hoisting because it doesn't involve an alpha rename to avoid capture.
Have JS developers taken a bunch of compiler theory terms without understanding them and applied them to Javascript? I have to say, I'm not really surprised...
In a lexically scoped language I would assume that the scoping follows the lexical representation of the code. The given example goes against this assumption. The variable i is not even defined before it is refered to in the inner function.
The level of scope (global, block, ...) interact with the type of scoping in many ways, so I would not say that they are orthogonal. This example would behave very differently under dynamic and lexical scoping.
function foo() {
var i = 1;
function bar() {
var a = i;
var i = 2;
return a + i;
}
return bar;
}
foo()() // => ?
In a lexically scoped language I would assume that the scoping follows the lexical representation of the code.
In a lexically scoped language the variables belong to the containing lexical scope. That that lexical scope can be a function rather than a block doesn't make Javascript any less lexically scoped (in cases other than the dynamic this).
The variable i is not even defined before it is refered to in the inner function.
The order that variables appear within a scope has no effect on the scoping rules.
For example, in C#:
void Foo() {
i = 10;
int i;
}
Gives "error CS0841: Cannot use local variable 'i' before it is declared". It knows exactly what variable "i = 10" is referring to because it's declared in the same scope, but the language designers have decided to add the rule that you're not allowed to reference it before it's declared (because doing so is most likely a bug).
Incidentally, Javascript made the opposite decision; you can refer to variables before their declaration, they're just "undefined". But in both cases the compiler knows what "i" it's referring to by the lexical scope.
I would argue that the order of declarations is part of the scoping rules. In your example the variable i is not yet in scope when the assignment is done, and thus the compiler complaints. But this is of course somewhat philosophical.
My first argument was that the order of statements is part of the lexical representation of code, and thus should effect the lexical scoping.
I'd say the order of declarations isn't part of the scoping rules.
The scoping rules are used for name resolution. In my example, both i's belong to the same scope, so the name resolution says "oh, that first i is the one in this scope, not any parent scope".
In languages like C#, a separate analysis is then done to find out if the variable is definitely assigned at each reference. This can include some sophisticated reachability analysis(how sophisticated depends on the usefulness/complexity tradeoff).
I guess it's all semantics really, but I prefer to keep the scope (ha ha) of lexical scoping rules closer to name resolution than start mixing in definite assignment and reachability analysis and the things that come with that.
It makes understanding and expressing the commonalities and differences between different language's lexical scoping a lot easier. C# is lexically scoped with blocks introducing new lexical scopes. Javascript* and Python are lexically scoped with functions introducing new lexical scopes. As an orthogonal concept, C# and Python disallow references before definite assignment, while Javascript allows it.
The point isn't that I don't like it (although I really don't), it's that Scheme doesn't work the same way as Javascript, and the Javascript-is-Scheme slogan contributes to non-understanding of those differences.
In particular I was bothered by the author's separation of scoping and closures. For example, if you look at what Objective-C has in terms of closures, and what JS has, then you're practically looking at completely different animals. But if you just say "closures ✓", "perfect lexical scoping ✗", you completely miss that point.
You are of course correct about with() being dynamically scoped; thank you. I had forgotten about with() when I wrote what I wrote. I must weaken my claim to "JS is lexically scoped, except for the with() construct."