Local By Default Rant

lua-users home
wiki

I would like to address the arguments given against having "local by default" be possible in lua, instead of the current "global by default" (upvalue by default) status for implicit variable declarations. I realize these aren't all arguments at all, but several folks on the IRC channel suggested that LocalByDefault was the go-to source for learning why "local by default" is a bad idea and "global by default" is fine.

First I'll outline my understanding of how "local by default" would work. It could be on a module-to-module basis, with some sort of pragma in the file to indicate that. Then I'll explain some reasons global by default is terrible. Finally I'll address the actual arguments presented on that page, if they are even arguments for "global by default" at all.

Upon assignment the first time to a variable, it creates a local variable by that name just as if "local" were specified. A keyword either "global", "nonlocal" or "upvalue" would indicate exceptions to that, when a variable is in fact an upvalue. The keyword could take a variable, a variable assignment, or several of those things as is convenient. What follows is a hopefully complete example of this syntax.

in a file, call it module.lua:

default local

upvalue foo, bar
print("All variables on the module level are local, except foo and bar")

a = "a.module"
b = "b.module"
c = "c.module"

function foo(a)
    c = "c.foo"
    print("c is local",c)
    print("a is local",a)
    print("b is upvalue",b)
end

function bar()
    c = "c.bar"
    print("c is local",c)
    print("a and b are upvalues",a,b)
    foo("arg.foo")
    assert(c != 23)
    assert(c != 3)
    assert(c == 4)
end

function baz(a,b,c)
    print("Everything is local in here",a,b,c)
end

baz(1,2,3)

function blech()
    print("Everything is upvalue in here",a,b,c)
end

blech()

function outer()
    upvalue b
    function inner(a)
        upvalue b,c
        print("b is upvalue, which is upvalue to module level",b)
        print("c is upvalue, to the outer function",c)
        print("a is local",a)
    end
    print("Now a will be arg.inner, b will be b.module, and c is nil")
    inner("arg.inner")
    a = "a.outer"
    c = "c.outer"
    print("Now a will be arg.inner, b will be b.module and c will be c.outer:")
    inner("arg.inner")

    b = "b.outer"
end

outer()
print("Whoops, b was an upvalue, so it modified the module's b to b.outer",b)

function outer()
    a = "a.ohno"
    function inner()
        upvalue a
        print("a is a.module wait no it's a.ohno",a)
    end
end
outer()

print("note outer, blech, baz, a, b and c are all module local variables. You can only call foo and bar when requiring this.")

(Note: this isn't valid Lua. It's my concept of what Lua ought to look like.)

An obvious limitation is that for an innerly declared function to access a module level variable by the same name as the outer function's local variable, the outer function will have to declare it an upvalue too. This could be mitigated with a numeric argument to upvalue for how many frames to go up. There could additionally be "global" and perhaps "module" keywords for absolute scope regardless of depth of nesting. Currently lua does nothing to mitigate this, but the instance where you run into this problem is sufficiently rare that people just work around it.

The reason that global by default is a bad idea is one of limiting the destructive scope of a human error. When you forget to specify local, and "local by default" is in effect, then your function continues to work perfectly. When you forget to specify "global" or "upvalue" or whatnot, then your function does not modify any of the program's global state, producing no side effects and making nothing else work differently. When "global by default" is in effect and you forget to specify local, then your function both works and does not work depending on what happened to that global variable during the function's progress. Additionally when your function changes the global variable mistaking it to be local, other functions where the same mistake was made will elicit the same behavior.

The reason that global by default is a bad idea is because globals are a bad idea. We do not need a language that makes their use easier than locals.

Point blank, if implicit declarations must exist at all, and not be a syntax error, then when we forget to specify the scope of our variable, global by default can cause unpredictable program state, side effects and can break formerly working completely unrelated code without altering that code. That's terrible! If global by default were a requirement then we might put up with this shiznit, but an alternative exists! Global by default is terrible and we should use local by default instead.

There's also the "copy-and-paste" issue, though that comes up more rarely. When you copy a code fragment from one place to another, it's possible to forget to select the "local" declarations at the top. Additionally you might have forgotten local declarations at all, so when you copy the code into a different function, then both functions will use the same global variable unintentionally It's practically impossible not to have both functions not blow up mysteriously for no reason when you do this.

Now, to address the arguments in favor of "global by default" and why they are miscast.

I personally used to think implicit declarations were bad, and that explicit declarations significantly reduced your errors and prevented bugs. Then I realized it did neither of these things, and thus got sick and tired of typing boilerplate just to satisfy some ivory tower eggheads whose claims have been soundly disproven by the success of implicit variables and their ease of use without errors cropping up. They claim it's more proper or better, but it's just one syntax versus another. Nothing says combining assignment and declaration is a bad thing. They can't assume something unproven (or even disproven), and argue for mandatory explicit declarations based on that assumption!

I might be a bit biased.

But if implicit declarations must exist, it is extremely important that they be local by default, to prevent very real and hard to track down programming errors. So, even if you don't singlehandedly rewrite all Lua code to be local by default, please stop saying that global by default is better.


RecentChanges · preferences
edit · history
Last edited January 5, 2014 10:42 pm GMT (diff)