Elisp is dynamically scoped. This means that on each
let, we push a new binding onto the environment stack, and on every variable reference, we walk up this stack to find the nearest (lowest-level) binding for this variable’s name.
This is generally undesirable for composability. For instance, if we were to construct a closure to be passed further down the stack, then upon evaluation, a free variable could end up referring to a different variable than what was intended (it will refer to whatever the nearest matching binding is). So, in
(map (lambda (x) (+ y x)) xs), we can only hope that
map itself doesn’t bind the variable
y, or else the occurrence of
y in our closure would be captured by
map‘s binding rather than the binding at our call site.
However, dynamic scoping can be useful at times. Case in point: Emacs has many global variables representing various editor settings. An example is
inhibit-read-only, which disables buffer protection for read-only characters. If you’re writing a function that needs to update these read-only parts of the buffer, you can use dynamic scoping to bind
inhibit-read-only, which will be effective across any lexical scope for as long as this binding is on the stack.
Common Lisp and Scheme are lexically scoped, like most modern mainstream languages. However, Scheme has a nice feature called
fluid-let, which provides dynamic binding by assigning a new value to the provided variable for the duration (dynamic scope) of the fluid-let, then restoring the original value upon exit. This plays nicely with continuations as well, so that (for instance) we safely restore/set the value upon context-switching into/out of the thread, respectively.
Apparently, the earliest versions of Common Lisp attempted to implement lexical scoping, but inadvertently wound up with dynamic scoping. They actually managed to change the language to use lexical scoping.
Other languages may achieve similar effects via implicit parameters. Implicit Parameters: Dynamic Scoping with Static Types shows how to add this to Haskell (Hugs implements something based on this).
Thanks to Austin for this discussion.