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. E.g.,
(define x 9)
(define add2 (lambda (x) (+ x 2)))
x => 9
(add2 3) => 5
(add2 x) => 11
x => 9
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
, i.e., 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! x 20)
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
:
(define add2
(lambda (x)
(set! x (+ x 2))
x))
The set!
here adds 2
to the local variable
x
, and returns that value. (In terms of effect,
this procedure is indistinguishable from the previous
add2
.) We can call add2
on the
global x
, as before:
(add2 x) => 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. E.g.,
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. E.g.,
(define counter 0)
(define bump-counter
(lambda ()
(set! counter (+ counter 1))
counter))
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
:
(bump-counter) => 1
(bump-counter) => 2
(bump-counter) => 3
let
and let*
let
introduces a list of local variables for use within its
body:
(let ((x 1)
(y 2)
(z 3))
(list x y z))
=> (1 2 3)
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 ((x 1)
(y x))
(+ x y))
=> 21
This is because x
is bound to 1
, and y
is
bound to the global x
, 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* ((x 1)
(y x))
(+ x y))
=> 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 let
s:
(let ((x 1))
(let ((y x))
(+ x y)))
=> 2
The values bound to lexical variables can be procedures:
(let ((cons (lambda (x y) (+ x y))))
(cons 1 2))
=> 3
Inside this let
body, the lexical variable cons
adds its arguments. Outside, cons
continues to
create dotted pairs.
fluid-let
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
(fluid-let ((counter 99))
(display (bump-counter)) (newline)
(display (bump-counter)) (newline)
(display (bump-counter)) (newline))
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 display
s 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.
To drive home this point, consider the program
(let ((counter 99))
(display (bump-counter)) (newline)
(display (bump-counter)) (newline)
(display (bump-counter)) (newline))
which substitutes let
for fluid-let
in
the previous example. The output is now
4
5
6
I.e., the global counter
, which is currently
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 global
counter
, whose final value is 6
.
counter => 6
2 fluid-let
is a nonstandard special
form. See sec. 7.3 for a definition
of fluid-let
in Scheme.