Next: , Previous: Chaining, Up: About Closure


3.1.4.4 Lexical Scope

The rules that we have just been describing are the details of how Scheme implements “lexical scoping”. This subsection takes a brief diversion to explain what lexical scope means in general and to present an example of non-lexical scoping.

“Lexical scope” in general is the idea that

In practice, lexical scoping is the norm for most programming languages, and probably corresponds to what you would intuitively consider to be “normal”. You may even be wondering how the situation could possibly — and usefully — be otherwise. To demonstrate that another kind of scoping is possible, therefore, and to compare it against lexical scoping, the following subsection presents an example of non-lexical scoping and examines in detail how its behavior differs from the corresponding lexically scoped code.

An Example of Non-Lexical Scoping

To demonstrate that non-lexical scoping does exist and can be useful, we present the following example from Emacs Lisp, which is a “dynamically scoped” language.

     (defvar currency-abbreviation "USD")
     
     (defun currency-string (units hundredths)
       (concat currency-abbreviation
               (number-to-string units)
               "."
               (number-to-string hundredths)))
     
     (defun french-currency-string (units hundredths)
       (let ((currency-abbreviation "FRF"))
         (currency-string units hundredths)))

The question to focus on here is: what does the identifier currency-abbreviation refer to in the currency-string function? The answer, in Emacs Lisp, is that all variable bindings go onto a single stack, and that currency-abbreviation refers to the topmost binding from that stack which has the name “currency-abbreviation”. The binding that is created by the defvar form, to the value "USD", is only relevant if none of the code that calls currency-string rebinds the name “currency-abbreviation” in the meanwhile.

The second function french-currency-string works precisely by taking advantage of this behaviour. It creates a new binding for the name “currency-abbreviation” which overrides the one established by the defvar form.

     ;; Note!  This is Emacs Lisp evaluation, not Scheme!
     (french-currency-string 33 44)
     =>
     "FRF33.44"

Now let's look at the corresponding, lexically scoped Scheme code:

     (define currency-abbreviation "USD")
     
     (define (currency-string units hundredths)
       (string-append currency-abbreviation
                      (number->string units)
                      "."
                      (number->string hundredths)))
     
     (define (french-currency-string units hundredths)
       (let ((currency-abbreviation "FRF"))
         (currency-string units hundredths)))

According to the rules of lexical scoping, the currency-abbreviation in currency-string refers to the variable location in the innermost environment at that point in the code which has a binding for currency-abbreviation, which is the variable location in the top level environment created by the preceding (define currency-abbreviation ...) expression.

In Scheme, therefore, the french-currency-string procedure does not work as intended. The variable binding that it creates for “currency-abbreviation” is purely local to the code that forms the body of the let expression. Since this code doesn't directly use the name “currency-abbreviation” at all, the binding is pointless.

     (french-currency-string 33 44)
     =>
     "USD33.44"

This begs the question of how the Emacs Lisp behaviour can be implemented in Scheme. In general, this is a design question whose answer depends upon the problem that is being addressed. In this case, the best answer may be that currency-string should be redesigned so that it can take an optional third argument. This third argument, if supplied, is interpreted as a currency abbreviation that overrides the default.

It is possible to change french-currency-string so that it mostly works without changing currency-string, but the fix is inelegant, and susceptible to interrupts that could leave the currency-abbreviation variable in the wrong state:

     (define (french-currency-string units hundredths)
       (set! currency-abbreviation "FRF")
       (let ((result (currency-string units hundredths)))
         (set! currency-abbreviation "USD")
         result))

The key point here is that the code does not create any local binding for the identifier currency-abbreviation, so all occurrences of this identifier refer to the top level variable.