When I first discovered that there was a thing called “dynamic scope”, I was amazed that there was actually another way of doing “scope” other than lexical scope. Part of the amazement was the thought, “why would anyone want to do it that way, it seems so error prone!”. Apparently others agreed, because you don’t see dynamic scope much in modern languages anymore.
For those who don’t know what dynamic scoping is, let me give a quick demonstration in a fictitious language:
string x = "Hello World!";
print(x); // Prints "Hello World!" if called from foo
In the above example,
x is in scope in
bar not because
bar knows about
foo but because
bar is called by
foo at runtime.
At first glance dynamic scoping seems terrible. It reminds me of programming in assembly, where a called routine may refer to the “variable”
EAX, whose value is dynamically determined by the most recent time EAX was set – be it the caller code or anywhere else. Surely this is a bad thing?
Wikipedia points out that,
Dynamic scoping also voids all the benefits of referential transparency. As such, dynamic scoping can be dangerous and few modern languages use it
In other words, we can’t really tell what a function will evaluate to because it might use variables we didn’t pass to it, whose values depend on where the function is currently being called from.
As a side note, I think Wikipedia is wrong in this case . The reason is simple: we could just as easily pass the dynamic environment into each function as an argument. Arguments are the bridge between lexical and dynamic scope: the parameter value is determined dynamically based on the context of the function’s execution rather than its lexical context, but its identifier is determined lexically by the parameter name. So functions accessing dynamically scoped variables can still be referentially transparent if the dynamic context is considered to be syntactic sugar for an additional parameter that holds the dynamic environment.
That technicality aside though, dynamically scoped variables are still quite different to lexical scoping in practice. For example a function can access a variable that is multiple frames down in the stack, and it could be quite difficult to predict what value a particular variable might have just by looking at the program.
The flip side
I think dynamically scoped variables can actually be a good thing.
Here’s the reason: global variables.
Global variables are bad . At least that’s what we’ve been taught, and I partially agree.
But what happens when we literally need a single instance of something?
One example is a program. We only need one program in our program , and the solution is that we normally need a globally defined
main function. How would you write a program in C# or Java if they didn’t have a way to say
public static main?
main is a stupid example of a global variable. What about
std::cout in C++? In this case a program has a console interface, and
cout is a global variable that represents that interface. One console = one
cout. What a pain it would be if
cout wasn’t global. We would have
void main(ostream& cout...).
In other words, there are places where a global variable just makes sense. These are cases where there really is just one instance of something (or any fixed number of something). All the non-member functions in the C and C++ standard libraries are examples of this. We have one heap, so we have one global
malloc function. We have one operating system, so we have one global
I can easily extend these principles to functions and classes I’ve written myself. I have a program that accesses one database, reads from one config file, interacts with one GUI. I think all of these qualify just as easily as correct places to use global variables.
What happens when there are two?
What happens when a piece of program code normally writes to the console, and now I want it to write to a log file? What happens if it uses a native GUI, and now I want to use a web interface, or have multiple client GUIs? What happens if it connects to a database, but now in my unit tests I want it to connect to a mock data source? In these cases, hard-coding access to
cout or to
myDatabase seems like a bad design decision.
Enter: Dynamic scoping
I think dynamic scoping is a perfect fit here, and I probably don’t even need to explain my reasons because I’m sure you can see exactly where I’m going with this. You can have whole chunks of your program just “assume” that there’s a thing called a “database” without caring where it comes from.
Using dynamic scoping has a decoupling effect. Functions right at the top of the stack can access environments near the bottom of the stack without coupling to anything in between. Nothing is coupled directly to global variables or functions. If you want to override the heap and define your own malloc functions for a particular branch of the program, it’s as simple as pie.
It could reduce code. For an entire module that’s intended for for data manipulation it makes no sense to manually “thread” the database variable as a parameter through the call tree – it’s just work for nothing. The developers implicitly know there is a database accessible from any function in the module, so why can’t the language support that?
It’s an ideal replacement for global variables. In any situation where a global variable might have been a good choice, a dynamically scoped variable will be a better one, and probably better than dependency injection with lexical scoping in these cases. In fact why not just define the whole program as having a “root dynamic scope” which contains all the “global variables” – then any function can override global variables at any time.
I have a few final things to say. One is that dynamic scoping doesn’t mean dynamic typing. I think it’s entirely reasonable to have a static type checker enforce the type bindings of a dynamic scope environment. A function has certain environment requirements, and it implicitly adopts the requirements of the functions it calls. The environment requirements become implicitly part of the published type signature of the function.
Finally, let me say that I don’t intend a language to only support dynamic scoping. That would be crazy – lexical scoping is much more sensible 99% of the time. Dynamic scoping is better than global variables, and it’s better than lexical scoping in some situations.
I really think dynamic scoping has promise, and we shouldn’t throw the baby out with the bath water. Yes it’s dangerous, but don’t take the chainsaw away from the lumberjack.
Have you ever worked in a language with dynamic scoping? I’d be interested to hear what your experience is and if you agree with what I’m saying.