[   ^ to index...   |   next -->   ]

CSE 341 : 3 May 2001

Lexical scoping and nested functions

An environment defines a set of bindings between symbols and their values:

val a = 15;
val b = "foo";
val c = fn (n, p)
        => p = (n * p);    
val d = [1,2];
            
[global environment diagram, mappings from identifiers to values]

When you evaluate a let expression, you create a new environment whose bindings temporarily shadow those in its parent environment. You can visualize this as sub-environment with a link to its parent environment; evaluation begins at the current environment, and proceeds upwards in the parent chain:

let
    val a = 20   
in
    hd(d) + a
end;
            
[let environment diagram]

Nested functions can access environments in surrounding scopes; this is known as lexical scoping. To support lexical scoping, however, a function must remember the environment in existence when the function is defined. A function value therefore must be a pair of items: a pointer to the parent environment, and a pointer to a procedure (so, the above diagrams are inaccurate). This environment/procedure pair is known as a closure:

(* Declare f *)
fun f(x, y) =
    let
        fun nested(z)
            = a + x + z  
    in
        nested(y)
    end;
            
[fn-local environment diagram]

Keunwoo Lee
Last modified: Wed May 2 20:49:11 PDT 2001