Lexical scope vs. Dynamic scope

We definitely can’t do it at this point, but it would be interesting for a future language to semantically guarantee that the value of a global will only be read and written to once by a function. (i.e. it captures the value on first use and writes to the value on last use).

I don’t see the point in making this a semantic guarantee, but since a read from a global is monotonic, this is already a valid optimization. Right now, it’s mostly just a matter of enabling LLVM to better reason about this.

It may be semantically valid in terms of memory models but if someone expects changes to globals to be immediately visible then this would be very surprising behavior. They could write code like this:

STOP::Bool = false

function main()
    while !STOP
        do_stuff()
    end
    global STOP = false
    return
end

function do_stuff()
    print(".")
    global STOP = rand() < 1e-3
end

main()

Is this bad code? Indubitably. But people would be pretty confused if main() never terminated.

2 Likes

Yeah, this optimization would not be valid if the function do_stuff isn’t inlined. You can still run into concurrency bugs very similar to this when using multiple threads though, which is why you can actually specify the atomic ordering for reading from a global in 1.9 (see https://github.com/JuliaLang/julia/pull/44231).

1 Like

The topic of this thread is Lexical Scope versus Dynamic Scope.

Indeed, code that is lexically scoped is much easier to understand, easier to reason about, and more efficient to execute.

Dynamic Scope does have it’s place though. Once every few years I find myself missing it. It is obvious when others need it too. The typical hack to work around the absence of dynamic scoping is for a library to introduce a “context” object which must be passed in by user code, passed around by all functions of that library, and passed to any user code that the library must call back to. One such example is

but I think I have also seen this hack used in Julia libraries as well.

Suppose there is some library that your program needs to call in to, and that library will then call some inner part of your program. Now suppose the outer part of your program needs to communicate some piece of data to the inner part of your program and that piece of data is irrelevant to the library. Without dynamic scoping (or a kludgy implementation of it) there is no way to communicate that data in a thread safe manner.

3 Likes

In case anyone’s interested other programs that mess with variable scoping, R does this. Having something conceptually similar may be useful for metaprogramming across function boundaries, but like most things in R it’s pretty messy and doesn’t translate well into something that could be easily type stable for writing simple code.

2 Likes

Yes, dynamic scope has its place as a “better global”. We do have Base.task_local_storage(), though that comes with some caveats. There’s a package and PR prototyping an improved version here: