Lexical and Dynamic Scope Usage
Do you keep forgetting the difference between lexical and dynamic scoping? Perhaps it helps to think about when you would pick one over the other.
In programming languages we frequently talk about whether variables have lexical or dynamic scope.
However for some reason lexical and dynamic scope is one of those things I keep having to look up, because I always forget it. I am sure I am not alone, so this is an attempt at coming up with examples and associations which will help you remember.
We could look up the dictionary definition of Lexical:
of or relating to the words or vocabulary of a language, especially as distinguished from its grammatical and syntactical aspects.
It may give a clue that lexical scoping is about determining scope based on body of text itself. The code as you read it. It you do not have to consider how the code is executed or how the state of your environment changes over time.
That is why we tend to prefer lexical scoping. You can simply examine the code to figure out where the value of a variable comes from. So other possible ways to help you remember lexical scoping is that we could also call it:
- Textual scope. The text as it stands decides the value of a variable.
- Static scope. The compiler should be able to figure out how the variable is assigned a value. No need to run the program.
Enough Musings! Give Me a Code Example!
Okay, you want to cut to the chase. So let us build up an example. I like science so let us do some Newton motion equations. This is the the velocity
v at time
t if initial velocity at time
v = a*t + v0
Usually in these cases we want to express this as a function of
t and we don’t want to pass the acceleration
a and initial velocity
v0 in as arguments. Hence we write code like this:
a = 4
v0 = 10
velocity(t) = a*t + v0
This is Julia code, so we could use the Julia command line (REPL) and use this function like this:
And whenever you want you could change the parameters
v0 use to alter this calculation:
julia> v0 = 100
104julia> v0 = 0
But what if we wanted to reuse this velocity function to create a velocity function for say the moon or something and everything worked different there. Maybe somehow we got better start velocity.
a = 10
v0 = 100
Let us try this in the Julia command line (REPL):
Nope, that doesn’t work so well. Why? Because Julia does not use dynamic scoping. The velocity function is not going to look for the acceleration
a and initial velocity
v0 in the scope it gets called from. It looks for these parameters based on where they where defined in code relative to the definition of the
velocity function. In other words it is textual. You look at the code around.
Yet this kind of functionality would be kind of useful, which is perhaps why people initially thought dynamic scoping was a good idea. So how do we typically solve this problem in a language with lexical scoping? We wrap our velocity function inside another function so we can create a new lexical scope over and over again capturing different varibles:
function make_velocity(a, v0)
a*t + v0
Now we can make
moon_velocity functions like this:
velocity = make_velocity(4, 10)moon_velocity = make_velocity(10, 100)
When Do You Want Dynamic Scope?
So do we need dynamic scope at all? Turns out there are a number of cases where it is useful. E.g. when you launch a program in a Unix system e.g. from the terminal you basically got dynamic scope.
The new process inherits the environment from the parent process (the terminal), with things such as environment variables. In this case that is desirable behavior. You don’t want environment variables for a process to be bound to wherever the program code was written (lexical scope).
You are not going to be looking at code in that context anyway. In this case the runtime environment is more natural to think of as the starting-point.
Dynamic Scope for Loggers
Another example where we would want dynamic scope is for loggers. In many logging frameworks the logger is quite static:
logger.warning("something bad happened, fix it!")
The module has some variable
logger which is used as the logger. That means you have fixed for the whole module, class or whatever granularity one operates with, what the logger should be.
That is inflexible. In this case we want dynamic scope. We want the runtime environment, the code calling another function to decide what logger that specific function should use, right there and then. In another context you may want it to use another logger.
This is a bit of a contrived example so bear with me. It is supposed to illustrate that you potentially have a deep call stack where the same function can be called in different contexts:
@info "my log message" x
In this case we got
inner_dummy which is called twice from
outer_dummy. What I want to show here is that the function is not permanently bound to one specific logger. The dynamic environment decides what the logger is.
To demonstrate this we must make some alternative loggers to use:
io = open("log.txt", "w+")
logger = SimpleLogger(io)
Now we can call
outer_dummy using this logger as the context:
@info "hello world"
When done we close the iostream
io , so we can look at the content of the log file
log.txt which we created.
If we run this in the terminal we get the following log message in the terminal window:
┌ Info: my log message
└ x = 13
However in the
log.txt file we get:
┌ Info: hello world
└ @ Main REPL:2
┌ Info: my log message
│ x = 3
└ @ Main REPL:2
Conclusion, You Need Both
So while at the programming language level we typically prefer lexical scoping, dynamic scoping still is useful in various contexts. You are not always in a situation where reading the source code to determine how a variable is bound can be determined. You cannot do that with processes in the Unix shell e.g.
And the predictable nature of binding a variable in the scope, that you desire for most things in your source code is not desirable for a logger. In this case you want to be able to switch around the loggers. You don’t want the source code to predetermine the logger you use. The caller should decide.
Hopefully with some of these practical examples, motivating the use of each type of scope it will be easier to remember until next time, so you don’t have to look up the definition every time like me 😉