Proposal: opt-out local scoping

Yuh-Ruey Chen maian330 at gmail.com
Thu Aug 28 00:28:52 PDT 2008


Well we're all discussing radical new syntaxes (I'm looking at you,
Ingvar), I might as well propose my own.

One thing about JavaScript that has always really bothered me (and
probably several of you) is how JavaScript has opt-in local scope,
rather than opt-out local scope. That is, in order to have an identifier
reference a local variable, there needs to be a |var| declaration that
says that that identifier refers to a local variable, and not non-local
one (either in a surrounding function or the global scope). This has a
couple related problems:

1) Lack of integrity - in that it's easy to shoot yourself in the foot.
One missing |var| and you can be debugging some random error in the
wrong place.

2) It's inconvenient. This is pretty self-explanatory. It also
encourages the usage of redundant var declarations in
often-copied/pasted code, even if it may not be necessary due to var
hoisting, which is apparently a practice some people here are not fond of.

3) It can be confusing due to var hoisting. Suppose you have code like this:

blah = 10;
(function() {
    print(blah);
    var blah = 20;
    print(blah);
})();

The novice user would expect this to print "10" followed by "20", when
it really prints out "undefined" followed by "20".

This has all been discussed to death before, I'm sure, with the
conclusion that changing the behavior is backwards incompatible. And I
vaguely remember someone saying that we shouldn't add a pragma to "fix"
this issue, although the reason for that escapes me at the moment.


So here's the proposal that is backwards compatible: provide a block
that changes the "default" scoping.

    var { ... }

where everything assigned in ... results in a variable local to that
block. Note that this only affects _assignment_. You can still read from
a variable from a surrounding scope.

As some extra sugar,

    function name(args) var { ... }

would desugar

    function name(args) { var { ... } }

The default scoping setting also nests, i.e. it also crosses function
boundaries, so in

var {
    function name(args) { ... }
}

everything assigned in ... results in a variable local to the functions
block.

To escape (opt out) of the scoping setting, we need a new keyword.
Python 2.5 uses the keyword |nonlocal| for this purpose, so I'll use it
as well as a placeholder.

var {
    ...
    nonlocal x [ = y];
    ...
}

Finally, we can do the same thing for |let|:

let { ... }

etc.

Some examples:

---

function list(iterable) {
    var {
        if (iterable is Array) {
            lst = iterable;
        } else {
            list = [];
            for (x in iterable) {
                lst.push(x);
            }
        }
    }
    return lst;
}

function stats(iterable) var { // <-- notice var
    lst = list(iterable);
   
    function calcSum() {
        sum = 0;
        for each (x in lst) {
            sum += x;
        }
    }
   
    sum = calcSum();
   
    var mean;
   
    function calcMean() {
       nonlocal mean;
       mean = sum / lst.length;
    }
   
    return [lst.length, sum, mean];
}

---

which desugars to:

---

function list(iterable) {
    var lst;
    if (iterable is Array) {
        lst = iterable;
    } else {
        list = [];
        for (var x in iterable) {
            lst.push(x);
        }
    }
    return lst;
}

function stats(iterable) {
    var lst = list(iterable);
   
    function calcSum() {
        var sum = 0;
        for each (var x in lst) {
            sum += x;
        }
    }
   
    var sum = calcSum();
    var mean = sum / lst.length;
    return [lst.length, sum, mean];
}

---

And finally, it would be nice to have a pragma that can do this for us
(again, I don't recall the argument against them). Something like:

    use scope var;
    use scope let;
    use scope nonlocal; // default for backwards compatibility

which would obviate the need to add all these |var { ... }| and |let {
... }| statements in new code.

For example, the following would be equivalent to the above examples:

---

use scope var;

function list(iterable) {
    if (iterable is Array) {
        lst = iterable;
    } else {
        list = [];
        for (x in iterable) {
            lst.push(x);
        }
    }
    return lst;
}

function stats(iterable) {
    lst = list(iterable);
   
    function calcSum() {
        sum = 0;
        for each (x in lst) {
            sum += x;
        }
    }
   
    sum = calcSum();
   
    var mean;
   
    function calcMean() {
       use scope nonlocal;   // notice that these pragmas can be local
to blocks
       mean = sum / lst.length;
    }
   
    return [lst.length, sum, mean];
}

---

I should also point out that this is not a radical new concept. Both
Ruby and Python have had this functionality for a while. Comments?

-Yuh-Ruey Chen


More information about the Es-discuss mailing list