More on the Environment Model ----------------------------- Recall from last time: ENVIRONMENT: sequence of FRAMES FRAME: set of BINDINGS, at most one per name BINDING: variable name and its value We saw rules for: * VARIABLE LOOKUP -- first frame of environment that has the variable * DEFINE ----------- add a binding to the top-level frame (the last one in any environment--global frame) * SET! ------------- change the binding for the variable. Today we will see rules for * LAMBDA * EVAL * APPLY ---------------------------------------------------------------------- Big differences between environment and substitution models: in environment model, * EVALUATION always takes place with respect to an ENVIRONMENT * LAMBDA (or METHOD) packages up the current environment into a CLOSURE * APPLYing a procedure creates a new frame with bindings for the parameters, puts new frame at front of the environment in the closure, and the evaluates the body in that environment. ---------------------------------------------------------------------- Environment model rule for LAMBDA: Evaluating (lambda (params) body) in an environment e builds a PROCEDURE OBJECT consisting of: 1. The parameters, a sequence of variable names 2. The body (unevaluated) 3. A pointer to the environment e A procedure object is thus the (parsed) text of a function expression plus a pointer to the environment with respect to which the method expression was evaluated. This is the same as in the substitution model except for the pointer to the environment. ---------------------------------------------------------------------- Environment model rule for EVALUATION: To evaluate a compound expression wrt some environment e, 1. Evaluate the subexpressions wrt e 2. Apply the value in the first position to the remaining values. This is the same as the substitution model except for the reference to the environment e. Note that step 2 makes no mention of e. ---------------------------------------------------------------------- Environment model rule for APPLICATION: To apply a procedure object to a list of values: 1. Create a new frame with bindings that bind each formal parameter to the corresponding argument value. 2. Let e be the environment that is part of the procedure object being applied (see rule for LAMBDA above). Put the new frame created in step 1 on the front of e. Call this new environment e'. 3. Evaluate the body of the procedure wrt the environment e'. The key thing to note here is that in 3, the body is evaluated in the environment e', which is created from the environment that is part of the closure being applied, NOT the environment in which the compound expression causing the application was evaluated. ---------------------------------------------------------------------- Let's return to an example from last time: (define x 10) (define bar (lambda () x)) (define foo (lambda (f) (let ((x 15)) (f)))) (foo bar) ==> 10 Why 10 and not 15? Let's trace the creation of the environments. First we evaluate (define x 10). This puts a new binding in the global environment. Frame 0: +----------------------------------------+ | x: 10 | +----------------------------------------+ Now we evaluate (define bar (lambda () x)). This causes us to evaluate (lambda () x) in the current environment. This creates a new procedure object consisting of the parameter list (), the body x, and pointer to the current environment, which is the global environment. Let's call this procedure object M1. Then M1 is bound to the name bar, and that binding is placed in the global environment. Frame 0: +----------------------------------------+ | x: 10 | | bar: ----------------------------------+--> M1: Params: () +----------------------------------------+ Body: x Env: frame 0 Now we evaluate (define foo (lambda (f) (let ((x 15)) (f)))) This causes us to evaluate (lambda (f) (let ((x 15)) (f))) in the current environment. This creates a new procedure object M2 consisting of the parameter list (f), the body (let ((x 15)) (f)), and pointer to the current environment, which is the global environment. Then M2 is bound to the name foo, and that binding is placed in the global environment. Frame 0: +----------------------------------------+ | x: 10 | | bar: ----------------------------------+--> M1: Params: () | foo: -------------+ | Body: x +-------------------+--------------------+ Env: frame 0 | +-----------------------> M2: Params: (f) Body: (let ((x 15)) (f)) Env: frame 0 Now we evaluate (foo bar) in the current environment. This uses the EVAL rule for compound expressions given above. We evaluate foo and bar, which searches the environment using the lookup rule: start with the youngest frame and work back towards the global frame until finding a binding for that name. We find bindings for foo and bar in the global frame. Their values are M2 and M1, repectively. Now we APPLY M2 to M1. We use the APPLY rule above. Create a new frame with a binding for the formal parameter f of M2 bound to the value of the argument, which is M1. Place this frame at the front of the environment pointed to by M2. Frame 0: +----------------------------------------+ | x: 10 | | bar: ------------------+ | | foo: -------------+ | | +-------------------+----+---------------+ | | +----+------------------> M2: Params: (f) | Body: (let ((x 15)) (f)) | Env: frame 0 | +------------------> M1: Params: () Body: x +------------------> Env: frame 0 | Frame 1: | +------------------------+----------+ | f : -------------------+ | +-----------------------------------+ Now we evaluate the body of M2 in that environment. The body is (let ((x 15)) (f)). The bind creates a new frame with a binding for x bound to the value 15. Frame 0: +----------------------------------------+ | x: 10 | | bar: ------------------+ | | foo: -------------+ | | +-------------------+----+---------------+ | | +----+------------------> M2: Params: (f) | Body: (let ((x 15)) (f)) | Env: frame 0 | +------------------> M1: Params: () Body: x +------------------> Env: frame 0 | Frame 1: | +------------------------+----------+ | f : -------------------+ | +-----------------------------------+ Frame 2: +-----------------------------------+ | x: 15 | +-----------------------------------+ Now we evaluate (f) in that environment. This is a compound expression with a function symbol f applied to no arguments. We evaluate the symbol f using the lookup rule. Its value M1 is found in frame 1. We then apply M1 to the arguments using the APPLY rule. We create a new frame with bindings for the formal parameters bound to the arguments (there aren't any in this case, so the frame is empty). We put this frame AT THE FRONT OF THE ENVIRONMENT POINTED TO BY M1, which in this case is the one beginning with frame 0 (the global environment). Frame 0: +----------------------------------------+ | x: 10 | | bar: ----------------------------------+--> M1: Params: () | foo: -------------+ | Body: x +-------------------+--------------------+ Env: frame 0 | +-----------------------> M2: Params: (f) Body: (let ((x 15)) (f)) Env: frame 0 Frame 3: +-----------------------------------+ | | +-----------------------------------+ We then evaluate the body, which is x, in this environment. The value of x is obtained by the lookup rule, giving 10. ---------------------------------------------------------------------- Another example: (define make-mult (lambda (n) (lambda (x) (* x n)))) Frame 0: (global frame) +----------------------------------------+ | make-mult: ----------------------------+----> Params: (n) | | Body: (lambda (x) (* x n)) +----------------------------------------+ >> Env: frame 0 Now, if we eval (define double (make-mult 2)) we build a new little environment: +-----------------------------------+ | n : 2 | +-----------------------------------+ which points back to the global one --- why??? because that is the environment that the procedure named make-mult points to (i.e., that is part 3 of the procedure we are applying). Then we build a procedure object which points to this environment, because that is the body of make-mult which we are evaluating. Params: (x) Body: (* x n) ---------------------------------------------------------------------- Now, let's call (double 7) Make a new frame with +-----------------------------------+ | x : 7 | +-----------------------------------+ which points to n-frame for double. Evaluate the body (* x n) in this environment. We get (* 7 2), which is 14; just follow the lookup rule. ---------------------------------------------------------------------- Now, if we (define triple (make-mult 3)) we'd get another frame +-----------------------------------+ | n : 3 | +-----------------------------------+ The procedure object for triple is like that for double, but with a different environment for n. Each procedure that we create has its *own* *private* environment, with just n in it. * Nobody else sees n Key idea here: * Use environment structures as contexts for determining the values of names. * Closer to the implementation than the substitution model.