Lexical Scoping

Things are getting crazy now! We have environments popping up, getting passed around, and being modified. We have functions defined in Scheem that are recursively calling evalScheem. How does it all fit together? For any given variable reference in a Scheem program, how do we know which environment it will use? How do we know we created an interpreter that passes the right environment to our evalScheem function?

The Scheem language as we've implemented it in these lessons has lexical scope. Most modern languages these days including JavaScript and Scheme have lexical scope. Let's see how it works through an example Scheme program shamelessly stolen from Peter Norvig.

make-account in Scheme

The function make-account takes a beginning bank balance and returns a function that updates the bank balance. In the code, the variable a is defined to be a new bank balance of $100. The user then immediately spends $20 by calling a, and gets back the new lower balance.

The colored boxes indicate the scope of all the variables. The outermost scope is the global environment. We add make-account and a to this global environment. The yellow box indicates the scope of balance. The purple box indicates the scope of amt.

The scoping rule

The rule is that expressions can refer to any variable associated with any box they are inside of. For example, in the purple box the expression (+ balance amt) can refer to amt since it is inside the purple box, and to balance because it is also inside the yellow box. In the expression (a -20) it is not possible to refer to amt because the expression (a -20) is not inside the purple box.

If a language has lexical scope then it is always possible to draw colored boxes like this for any expression. No matter what happens when you run the program you can always figure out which binding will be used for every reference to every variable.

make-account in JavaScript

The example program is almost exactly equivalent to this slightly more verbose JavaScript program:

The makeAccount function is returning a closure that updates balance each time it is called. If you haven't seen this before, take a look at Closure lesson 8: Nested Functions and Closure lesson 9: Stateful Closures. It's mind-blowing stuff.

Connection with our Scheem interpreter

So how does lexical scope connect to our Scheem interpreter? When we defined how lambda-one worked we said that it evaluates to a function value that takes a single argument arg and binds the variable name to that value, then evaluates the body of the lambda-one in that environment.

The critical thing here is that the environment that the function body will be evaluated in is being created and specified when the function is being created, in the evaluation of the lambda-one. That means when a function is created we know exactly how the bindings will work out for when we evaluate the function. It doesn't matter what happens at runtime, when it comes time to evaluate the function we know that the environment made when the function was created will be used. That's lexical scope.



Prev Next