Static vs Dynamic Scoping
Scoping rules determine how functions get values
for their free variables -- variables that are not
defined within the function. Scheme, Java, and
Python, and most other modern languages, use
static scoping, sometimes called lexical scoping.
With static scoping free variables get their values
from the surrounding environment.
Static scoping contrasts with dynamic scoping, where free values
in a function get their values from the caller's environment. The
following example illustrates the difference:
(let ([x 5] )
(let ([f (lambda (y) (+ x y))])
(let ([x 23])
(f 1))))
With static scoping the value of x in the body of f comes from the
surrounding environment; it is 5, so (f 1) returns 6.
With dynamic scoping the value of x comes from the most recent,
or caller's environment and is 23, so (f 1) returns 24
Dynamic scoping is older than static scoping; it is
what language designers first figured out how to
implement. Dynamic scoping makes more sense
to some people. For the most part language
designers have decided that large programs are
easier to debug if functions are self-contained
and work the same way every time, regardless of
the context they were called from. This gives an
edge to static scoping, so that is the rule most
often applied in programming languages.
Changing MiniScheme from static to dynamic binding is very
easy. Lambda expression need only evaluate to a pair (params,
body) rather than a full closure (params, body, environment).
When calling (apply-proc p args) we evaluate the body of p in
the caller's environment, extended with bindings of the params
to the values of the arguments.
This only affects two lines of the entire interpreter.