Scheme Programming/Local Scope

This section defines local scope and variable scoping in general.
Variable scope concepts.
Scheme uses lexical scoping, which means that the variable scope can be
immediately seen from the program text and that the variable scope is
defined at compile time.
Scheme is block structured. A block here means a stretch of code lines
that have start and end points. Block defines one scope and a scope is
where a variable is defined (or bound). Blocks can be nested. There
are outer and inner blocks.
Top level scope is a special scope, since by definition is has no
outer scope. Top level scope is also called global scope. Any other
scope is nested, and all of them are affected by the outer scope or
scopes. Local scope is the same as the current scope.
Top level scope.
Top level scope is the outmost scope (block) in the program's scopes.
We define here a variable in the top level scope and evaluate its
value:
&gt; (define x 1)
&gt; x
1
Formally we can say that variable codice_1 is bound to
value 1. In general, top level scope contains global bindings which
can be procedures or data values.
Local scope.
New scope (i.e. inner scope to the current) can be created with codice_2
expression:
&gt; (let ((y 2))
&gt; y)
2
The value of a codice_2 expression is the last evaluated
expression within the body of codice_2. In this case we get the value
of codice_5, which is 2.
Variable codice_5 exists only in the inner scope:
&gt; y
However, variables from the top level are visible to all inner scopes:
&gt; (define x 1)
&gt; (let ((y 2))
&gt; (+ x y))
3
It is possible to use the same variable name in the inner scope. Even
when the variable in the inner scope has the same name, as in the
outer scope, they are different bindings and they are stored into
different locations. Scheme uses variable bindings from the innermost
scope where the variable identifier is found.
Thus we can "shadow" variables in the outer scope:
&gt; (define x 1)
&gt; (let ((x 7))
&gt; x)
7
&gt; x
1
The inner codice_1 exists only within the codice_2
expression, but the outer codice_1 exists before and after the
codice_2 expression.
There are no limits in the nesting depth of blocks:
&gt; (define x 1)
&gt; (let ((x 7))
&gt; (let ((x 13))
&gt; x))
13
&gt; x
1
Note that, the code above is only demonstrating nesting of scopes and
it does not make much sense in general.
Closures.
So far we have referred variables from "normal" expressions that
produce data values. However, we can refer variables from outer scope
also from procedures. The lexical scoping rules apply the same way to
procedures as for other expressions. In fact codice_2 can be
expressed as syntax extension of codice_12, so actually we
have referenced variables from procedures all the time.
When a procedure refers to a variable that is not bound within the
procedure itself (i.e. bound in the outer scope), a "closure" is
created. The external variables become part of the closure. This
connection is so strong that even if the scope of a variable is
outlived, it still exists in the closure. In that sense closures break
the normal block scope of a variable. This happens when a closure is
the passed value from an inner scope to the outer scope.
Here is a very simple example of a closure:
&gt; (define x 0)
&gt; (define inc-x (lambda () (set! x (+ x 1)) x))
&gt; (inc-x)
1
&gt; (inc-x)
2
codice_13 increments codice_1 by 1 each time it is
evaluated. The procedure does not take any
arguments. codice_13 refers to the external variable
codice_1. Also, it might be worth noting that it refers to
codice_17 as well. codice_17 is actually a variable in
Scheme that just happens to be bound to a procedure that adds numbers together.
Closures can be used in many ways. Higher order functions take other
functions as arguments and apply them, and an argument can be a
closure. Closures can also be used to emulate objects. They maintain
state in an external variable, and update the state in each
application.
Here is an example, where closure is used along with high-order
function codice_19:
&gt; (define x 1)
&gt; (map (lambda (num) (+ num x)) '(10 11 12))
codice_19 takes two arguments: a procedure and a list. The
procedure is applied to each element in the list and a new list
is created from results of the procedure application.
There are two scopes involved in this example: the top level scope and
the inner scope created by codice_12. codice_22
exists in the local scope of codice_12, but codice_1
does not. codice_1 is hence part of the closure created by
codice_12. codice_19 is part of a "normal"
expression, and it does not start a new scope.