Dynamic Scoping

Why on earth would a programming language go to the dark side and bring dynamic scoping back from the death?
Dynamic scoping is widely considered to be way too powerful, it's too easy to shoot yourself in the foot with. There are only a handful of languages providing dynamic scoping including Emacs Lisp and Logo. So why does Citrine opt for dynamic scoped variables instead of lexical scoping?

Lexical vs Dynamic

To answer that question, I first need to explain the difference between lexical and dynamical scoping. Dynamic scoping is the simplest to understand: when you refer to a variable, say 'x' the program will look whether x has been defined previously and give you the value of 'x'. The program does this by following the program flow. So if you refer to 'x' it will first look in the current function block. If 'x' could not be found in that block it will look back further, i.e. when you called the current block and so on. Here is an example:

greeter := Object new.
greeter on: 'greet' do: {
 Pen write: 'Hello ' + person.
}.
person := 'James'.
greeter greet.

This little code snippet above will print:

Hello James

It's easy to understand, because it's simple. We first create an object that responds to the message 'greet'. The greeter object will then use the string defined in the variable 'person' to perform the greeting. We set this variable right before we send the 'greet' message to the greeter. It's simple not? This is called dynamic scoping and it's highly frowned upon, because you know, things have to be complex! Otherwise you can't take it seriously!

In lexical scoping you're not allowed to access the variable 'person'. This is why JavaScript is such a pain-in-the-ass to learn. The programs in JS simply 'forget' what values have been assigned to variables. If you would like to perform the same operation in JavaScript you would have to pass the 'person' variable as an argument. However this has its drawbacks, for complex functions you easily grow a big list of arguments. So, in order to avoid this developers have designed really bizarre ways to circumvent the limitations of lexical scoping: dependency injection or variable binding. For example, they might opt to give the string 'person' to the constructor like this:

greeter new: person.

However to facilitate this, we first need to override the constructor of the object and create an entire infrastructure in our object to bring the variable from outside, i.e. person to the inside method that requires it: greet. Also, this leads to another problem: glue code. You now need some management code that introduces objects to eachother in order to work together. Some Java frameworks put this glue code in XML files describing how the dependency injection should take place. This is the zenith of useless complexity. Java developers generally are quite skillful in making simple things utterly complex. However JavaScript developers, using currying and variable bindings with ambigious meanings of this are also churning out volumes of Kafkian code every day.

The best thing is, all of this have been done with good intentions. The road to hell is paved with good intentions, and this is no exception to that rule. The reason why lexical scoping is dominant is because it is supposed to protect developers from create complex code. The basic idea behind lexical scoping is a good one: you can always see where a variable comes from. In Lexical scoping the variables are looked up using the programming text. So you can always see where it comes from. Here is an example in JavaScript:

createGreeter  = function() {
 var person = 'James';
 return function() {
  return 'Hello ' + person;
 };
}
var greeter = createGreeter();
greeter();

Once again, this will output 'Hello James'. However the variable person is now defined in createGreeter. The moment we invoke greeter() the variable person should be forgotten, but guess what, JavaScript knows better! It still has a value! namely 'James'. Even though we left the createGreeter() function lightyears ago. Maybe my brain is wired differently, but I found this very hard to understand. The reasoning behind this is that you can always 'track' the variables. In contrast, if you look back at the Citrine example, it's not clear from within the greeter itself where the variable 'person' comes from and what it's value is. This could lead to very nasty code. However there is a simple solution for that:

#provide the name in var 'person'
Pen write: 'Hello ' + person.

Yep, that's a comment. The big mistake of lexical scoping is that it tries to protect programmers from themselves. Lot's of programming languages try to create a better breed of developers by subjecting them to all kinds of restrictions: static typing, type checking, forced identation (yes you silly Python!), lexical scoping and so on. Soon, I foresee programmers are not allowed to do anything at all anymore! Instead of piling up restrictions, Citrine gives you the freedom to create powerful code again. Yes, dynamic scoping can be a dangerous thing, but combined with the overall focus of Citrine on readablity and simplicity I think it's quite manageable.

Advantages of Dynamic Scoping

As I have already explained, there are numerous advantages to dynamic scoping. I just like to list them here to summarize.

  • Powerful (less code needed)
  • No need large number of arguments
  • No need to pass arrays as arguments
  • No need to use dependency injection frameworks
  • No need to use service locators
  • No need to use globals

To summarize: it's just easier to inject a variable into another function or object without having to build an entire code cathedral to facilitate it. That boils down to: less code. And less code oftens means: less bugs.

Mistakes of Lexical Scoping

While we have already discussed that the basic ideas behind lexical scoped variables are sound, we also have to look at why it fails so hard.

  • It's confusing, you follow the flow of the program with your mind, yet with Lexical scoping you have to take into account the 'other flow' as well
  • It encapsulates too much, injecting values into functions or objects requires too much effort: meaning... too much code.
  • The premise of overview is obviously mitigated by the overhead of additional code: you don't see where a variable is coming from if your code file is 9000 lines long or your functions has 6 array parameters, this negates all benefits

But I also think, there's a more fundamental problem with lexical scoping: it tries to protect the programmer from doing stupid things. While noble, this IMHO the wrong approach. If you want better quality software, you have make the language simpler, more readable not make it a challenge to write programs at all. Especially not in a world where we need more people being able to talk to machines, not less.

go home