Scheme's variables have lexical scope, ie, they are
visible only to forms within a certain contiguous
stretch of program text. The global variables we
have seen thus far are no exception: Their scope is all
program text, which is certainly contiguous.
We have also seen some examples of local
variables. These were the lambda parameters, which
get bound each time the procedure is called, and
whose scope is that procedure's body. Eg,
Here, there is a global x, and there is also a
local x, the latter introduced by procedure
add2. The global x is always
9. The local x gets bound to 3 in the
first call to add2 and to the value of the global
x, ie, 9, in the second call to add2.
When the procedure calls return, the global x
continues to be 9.
The form set! modifies the lexical binding of a
variable.
(set!x20)
modifies the global binding of x from 9 to
20, because that is the binding of x that is
visible to set!. If the set! was inside
add2's body, it would have modified the local
x:
(defineadd2
(lambda (x)
(set!x (+x2))
x))
The set! here adds 2 to the local variable
x, and the procedure returns this new value of the local x. (In terms of effect,
this procedure is indistinguishable from the previous
add2.) We can call add2 on the
global x, as before:
(add2x) =>22
(Remember global x is now 20, not 9!)
The set! inside add2 affects only the local
variable used by add2. Although the local variable
x got its binding from the global x,
the latter is unaffected by the set! to the local
x.
x=>20
Note that we had all this discussion because we used
the same identifier for a local variable and a global
variable. In any text, an identifier named x refers
to the lexically closest variable named x. This
will shadow any outer or global x's. Eg,
in add2, the parameter x shadows the global
x.
A procedure's body can access and modify variables in
its surrounding scope provided the procedure's
parameters don't shadow them. This can give some
interesting programs. Eg,
The procedure bump-counter is a zero-argument
procedure (also called a thunk). It introduces
no local variables, and thus cannot shadow anything.
Each time it is called, it modifies the global
variable
counter -- it increments it by 1 -- and returns
its current value. Here are some successive calls to
bump-counter:
Local variables can be introduced without explicitly
creating a procedure. The special form let
introduces a list of local variables for use within its
body:
(let ((x1)
(y2)
(z3))
(listxyz))
=> (123)
As with lambda, within the let-body, the local
x (bound to 1) shadows the global x (which
is bound to 20).
The local variable initializations -- x to 1;
y to 2; z to 3 -- are not considered
part of the let body. Therefore, a reference to
x in the initialization will refer to the global,
not the local x:
(let ((x1)
(yx))
(+xy))
=>21
This is because x is bound to 1, and y is
bound to the globalx, which is 20.
Sometimes, it is convenient to have let's list of
lexical variables be introduced in sequence, so that
the initialization of a later variable occurs in the
lexical scope of earlier variables. The form
let* does this:
(let* ((x1)
(yx))
(+xy))
=>2
The x in y's initialization refers to the x
just above. The example is entirely equivalent to --
and is in fact intended to be a convenient abbreviation
for -- the following program with nested lets:
(let ((x1))
(let ((yx))
(+xy)))
=>2
The values bound to lexical variables can be
procedures:
(let ((cons (lambda (xy) (+xy))))
(cons12))
=>3
Inside this let body, the lexical variable cons
adds its arguments. Outside, cons continues to
create dotted pairs.
A lexical variable is visible throughout its scope,
provided it isn't shadowed. Sometimes, it is helpful
to temporarily set a lexical variable to a
certain value. For this, we use the form
fluid-let.2
This looks similar to a let, but instead of
shadowing the global variable counter, it
temporarily sets it to 99 before continuing with
the
fluid-let body. Thus the displays in the body
produce
100
101
102
After the fluid-let expression has evaluated,
the global counter reverts to the value it had
before the fluid-let.
counter=>3
Note that fluid-let has an entirely different
effect from let. fluid-let does not introduce
new lexical variables like let does. It modifies
the bindings of existing lexical variables, and
the modification ceases as soon as the fluid-let does.
which substitutes let for fluid-let in
the previous example. The output is now
456
Ie, the global counter, which is initially
3, is updated by each call to bump-counter.
The new lexical variable counter, with its
initialization of 99, has no impact on the calls to
bump-counter, because although the calls to
bump-counter are within the scope of this local
counter, the body of bump-counter isn't. The
latter continues to refer to the globalcounter, whose final value is 6.
counter=>6
2fluid-let is a nonstandard special
form. See sec 8.3 for a definition
of fluid-let in Scheme.