CS 1 Fall 2007

Big Idea

Procedures and the Environment Model

Wednesday, November 14, 2007


A procedure is a pair consisting of:

  1. the text of the procedure
  2. a pointer to the environment in which the procedure was defined

Every time we create a procedure (i.e. a lambda expression) in Scheme, it contains both of these things. An environment is a linked sequence of frames in which variables are associated with their current values. Each frame also includes a pointer to its parent environment, except for a special frame called the global environment which has no parent.

When we apply a procedure to its arguments, we create a new frame for the formal arguments of the procedure (i.e. the names of the arguments), and that frame's parent pointer points to the same environment the procedure itself points to. This means that the variables which are free in the lambda expression (not bound by the formal arguments or other internal definitions in the lambda expression) will be resolved in the environment in which the procedure was defined.

When we couple this specification with the fact that we can create procedures inside procedures, pass procedures as arguments to other procedures, and return procedures from procedures, we get some very interesting and powerful results.

Notably, when we return a procedure, it knows about and has access to all the variables which were defined (were in scope) when the procedure was defined. The variables may be in frames which were created by calling other functions which have long since terminated. The procedure, in effect, traps these environment frames for its later use.

This also gives us a way to give a procedure private, local state. Variables in these trapped frames may be (we can design them to be) inaccessible from any other procedure. This is called information hiding in programming terminology and it's a very powerful feature. It means that procedures can have their own state that cannot be accessed or modified by other procedures, and it allows a procedure to have state variables without polluting the global namespace (generating names that may clash with names used by other procedures). As such, this is a powerful technique for data encapsulation and modularity -- it gives us power to isolate the parts of a program that can access a variable, making it easier to reason about the program and guarantee that procedures only interact in intended ways.

Coupled with the message-passing style we introduced on day 12, this allows us to define "smart" data objects with internal, private state that can only be accessed through the defined interfaces (set of messages). Once encapsulated in this manner, we can limit the kinds of operations which can be performed on the data, further guaranteeing disciplined use of a data abstraction and making it easier to guarantee program behavior.

Understand this, and you are well on your way to mastering object-oriented programming.