Ok, so that clarifies your question and also that’s not what dynamic scope means. Sorry to be a pedant about this stuff, but you know I’m a programming language nerd and I can’t help myself. This doesn’t really matter since it’s not what you were asking about, but I might as well clarify terms here. Dynamic scope means that the callee inherits variables from the caller. So an example of that using Julia syntax would be this:
f() = 2*x
function g(y)
x = 3*y
return f() # f gets called with x from here inside of g
end
function h()
return f() # f will error because x doesn't have a value
end
This is how dynamic scope would work: local variables from g
are visible inside of f
but only when f
is called from g
. If f
is called from h
which doesn’t define x
then there’s an undefined variable error. Yes, this is confusing and super error prone. What fundamentally is wrong with it? Just looking at f
you can’t really tell what the heck is going on. And that’s the direction modern languages have evolved: towards making it easier to understand strictly locally what some code does.
Now, here’s lexical scope:
function g(y)
x = 3*y
f() = 2*x # refers to x defined in g
return f()
end
This exaample looks dumb—why introduce f
at all?–but it shows that f
inherits x
from the lexically, as in syntactically, containing scope. Why is this better? Because you can look at the code and see where x
comes from. Compare this with dynamic scope where you cannot possibly know where x
inside of f
comes from because it could come from anywhere!
These examples are all with local variables. If we delete the g
shell around the last example, we get an example of accessing a global variable from inside of a function body:
y = 5
x = 3*y
f() = x*2 # refers to global x
Your question seems to be: why allow this at all if it’s a performance problem. Suppose we didn’t and required x
to be an argument. Then the code would look like this:
y = 5
x = 3*y
f(x) = x*2
Would the code work then? It would not. Why? Because it still refers to a global variable: *
. That’s right. In Julia, *
is not an operator, it’s just a global variable that happens, by default, to be bound to a generic function that performs multiplication. In a fresh REPL (or module) you can bind it to anything you want:
julia> * = "foo"
"foo"
julia> 1 * 2
ERROR: MethodError: objects of type String are not callable
Stacktrace:
[1] top-level scope
@ REPL[2]:1
The syntax 1 * 2
is equivalent to *(1, 2)
and it fails for the same reason that doing "foo"(1, 2)
would fail—you can’t call strings like functions.
<aside>
You can actually change that if you want by defining a call method for strings:
julia> (s::String)(a, b) = "$a $s $b"
julia> "foo"(1, 2)
"1 foo 2"
julia> 1 * 2
"1 foo 2"
Useless, but fun!
</aside>
So that’s why you can’t disallow accessing global variables: because if you did, then you wouldn’t be able to use any functions, not even arithmetic operators. However, functions are given constant bindings, so they aren’t a performance issue like other globals are. What about requiring globals to be constant? We could do that, but that’s not how any other languages work. People expect to be able to do things like this in the REPL or in scripts:
x = 123
x += 5
Even static languages allow you to change the values of globals, they just don’t allow changing the type. Ok, so what about that—disallowing changing types of globals? That’s getting more plausible and is something I even proposed for the language pre-1.0, but ultimately, it’s too far from how people expect their dynamic languages to work. People expect to be able to do x = 123
in the REPL and then later move on to some other work and do x = "Hello!"
and have that work.
There are other variations one could try like: you can have non-constant globals but you’re not allowed to use them from functions. Ultimately, that’s just not Julia’s style. The language generally lets you do whatever you want and won’t give you a hard time about it and assumes you know what you’re doing. We try to educate people about what is and isn’t a good idea in different situations, but Julia just isn’t a nanny state kind of language. Sometimes you don’t care about performance and want to use a non-constant, untyped global, and who are we to stop someone from doing that?