Sunday, January 2, 2011

Lexical Scope vs. Dynamic Scope

Most languages have lexical scope, nowadays. This is good news, as it is easier to use. In fact, many programmers do not even know the difference between lexical and dynamic scopes, leaving the distinction to programming language junkies.

In languages with lexical scope a variable name is resolved to the object with the same name in the nearest lexical environment. In other words, the compiler chooses the innermost object considering local variables, function parameters, enclosing function variables, global variables and so on. If it is not clear, by the description I gave (which is not clear, I admit) just think to C or Java or Python. That is lexical scope. It is also called static scope, as it can be statically determined with just the source and no runtime information.

It is good because the one who writes the function knows the environment where it is defined (and that is what matters), while the one who calls it does not need to know anything about it:  just to pass the parameters. On the other hand, with dynamic scope functions access the object with a given name that is *nearer* on the call stack. That is to say... the caller environment influences the function behaviour.

Early Lisp dialects have this strategy, as well as Emacs Lisp. Luckily, Common Lisp default mode is lexical scope (but dynamic scope can be used as well). Perl has dynamic scope (with optional "my") static scope.

A good example of the two different strategies is this snippet:

(let 
((f (let ((x 1))
(lambda () x)))
(x 0))
(funcall f))


If that snippet is evaluated in emacs *scratch* buffer (C-j) the result is 0. This is because when f is executed, the x nearest in the call stack is the one labeling 0. On the other hand a common lisp compiler would evaluate the thing to 1, as the x referenced in the lambda function is the one in the lexical closure.

* (let 
    ((f (let ((x 1))
          (lambda () x)))
     (x 0))
  (funcall f))
; in: LAMBDA NIL
;     (LET ((F
;            (LET (#)
;              (LAMBDA # X)))
;           (X 0))
;       (FUNCALL F))
; 
; caught STYLE-WARNING:
;   The variable X is defined but never used.
; 
; compilation unit finished
;   caught 1 STYLE-WARNING condition

1


, ,

2 comments:

Valerio said...

Congrats, very interesting post ;)

I usually appreciate posts about compilers and concepts of programming languages and your explanation on different scoping mechanisms is precise and clear.

That's interesting because most programmers tend to ignore at all such topics ;)

Unknown said...

Thanks!