NEW TOPIC AREA -- assignment and state variables The *critical* element of the course so far is the substitution model. We're about to step beyond it (Newton to Einstein). So far, all of our programs have been FUNCTIONAL PROGRAMS (Well, almost all -- e.g., not I/O): They can be thought of as pure mathematical functions. (foo 10) --> 18 (foo 10) --> 18 etc,... If foo is a mathematical function, (foo 10) will *always* return the same thing * As long as foo hasn't been redefined. Stop and think about this for a minute: we have written very sophisticated programs without using assignment. You probably wouldn't have believed it at the beginning of the semester, being := addicts from Pascal, or = addicts from C/C++. ---------------------------------------------------------------------- Now, we change all that. There is a special form in Dylan called set! (set! symbol expression) which CHANGES the value associated with symbol to the value of the expression. (set! x (+ 1 x)) * symbol is NOT evaluated * expression IS evaluated It is illegal to set! a symbol that doesn't exist. We CREATE variables by * define * bind * parameters to a procedure set! just CHANGES the values of an existing variable. (define (a ) 10) ;; a is now 10 (set! a 20) ;; a is now 20 (set! a 'x) generates an error, because a is a variable of type , but the value we are trying to assign it is a Note: this is why it's often useful to say (define (a ...) instead of (define a ...) SUBTLETY (beyond the scope of this course): side-effects introduce a notion of time into programs. That's why they are (a) necessary and (b) problematic. The value of a set! form is undefined -- it is used for its EFFECT, not its VALUE: e.g., (set! a 20) does not have a defined value. Expressions in Dylan either used for value or effect: most define/add-method Dylan set! exprs print VALUE yes no EFFECT no yes set! can be useful for programming, as we will see. [not as useful as all you := junkies think though] ---------------------------------------------------------------------- It also trashes the substitution model. (define (put-in-bowl ) (method ((thing )) (set! thing (cons thing '(in a bowl))) thing)) If we try to use the substitution model on put-in-bowl, we'll substitute whatever argument is passed for the parameter thing, and thus the model says that the function will always return whatever value was input. That is (put-in-bowl 'goldfish) will (according to the substitution model): 1. Substitute goldfish for thing EVERYWHERE in the body 2. Do something to handle set! -- doesn't really matter what 3. Return the final thing, which is {goldfish} So, the model says it will return goldfish, which is wrong, it returns (goldfish in a bowl) ---------------------------------------------------------------------- We need some way to talk about changing values. Substitution fails: cannot simply substitute a value for a name, because the value of a name may be changed. But there may be many different variables with the same name (define (x ) 'barney) (bind (((x ) 'purple)) (set! x 'monster) (print x) ;; monster ) (print x) ;; barney Since there can be many different variables called x at the same time --> Must change the *right* one. --> Need a model that lets us take care of that. We need the concept of how variables can have values in such a manner that allows the notion of "changing a variable's value" (doesn't make sense in substitution ) ---------------------------------------------------------------------- The substitution model was based on the idea that you could REPLACE EQUAL VALUES WITH EQUAL VALUES ignoring the context. If 'state' is defined as 0, you could replace 'state' by '0' and all is well. a concept known as REFERENTIAL TRANSPARENCY. set! destroys referential transparency -- rather, you must examine (carefully!) the context to determine when two things are always equal in that context. ---------------------------------------------------------------------- We're going to introduce a NEW evaluation model, called the ENVIRONMENT MODEL * Extension of the substitution model, to a more general naming model. The only thing that changes is how to determine values of variables -- no longer by substitution. Alternation of eval/apply is the same: () and [] * More "operational" - That means, closer to how the computer actually does things. But still not what it really does. - If you really want to know, CS314 will tell you An expression only has a value with respect to an ENVIRONMENT * which tells the values of the variables. ANALOGY TO REAL LIFE: * Names (=words) have multiple bindings (=meanings) depending on where you are. BOOT: * Computer lab -- restart a computer * Shoe store -- thing to wear on your feet * Parking ticket -- nasty thing clamped on the wheel of your car * England -- trunk of your car. Each ENVIRONMENT produces a different BINDING for a name. ---------------------------------------------------------------------- Definitions: An ENVIRONMENT is an association of names with values: * An ordered sequence of one or more FRAMES where a FRAME is an (unordered) set of BINDINGS. where a BINDING is an association of a VALUE to a NAME. Here's are three frames, in order, hence an environment (one or more frames in sequence) +------------------------------------+ | x:10 +: {Proc ...} | | y:20 | | z:30 | +------------------------------------+ ^ | +------------------------------------+ | x:() | | w:foobar | | | +------------------------------------+ ^ | +------------------------------------+ | x:oh | | y:(list of stuff) | | | +------------------------------------+ The value of a NAME is *always* determined with respect to *some* ENVIRONMENT. * We're going to have new rules for eval and apply, with respect to environments. * Extending the substitution model rules. LOOKUP RULE: * The value of a name is the value associated to that name in the FIRST frame of the environment that has a binding for that name. * (It is undefined if there aren't any bindings) Example of Lookup Rule: If the environment is that one above, the value of x is oh * (since the frame with x:oh is first) the value of w is foobar * (no binding in first frame) Note: the old value of x is *shadowed* by the new one. ---------------------------------------------------------------------- define rule: Evaluating a global expression adds a binding to the global environment (the top-level frame, which is special because it persists "forever"): >>> global frame = top one <<< BEFORE: top frame is: +------------------------------------+ | x:10 +: {Proc ...} | | y:20 | | z:30 | +------------------------------------+ AFTER (define (d ) (+ 5 7)) +------------------------------------+ | x:10 +: {Proc ...} | | y:20 d: 12 | | z:30 | +------------------------------------+ Note that define only alters the top-level frame, and only works at top-level interpreter, not in code. ---------------------------------------------------------------------- SET! rule: Evaluating (set! var newval) in an environment * Look up where the value of `var' lives * Change that binding to be `newval' (set! x 'otter) +------------------------------------+ | x:10 +: {Proc ...} | | y:20 d: 12 | | z:30 | +------------------------------------+ ^ | +------------------------------------+ | x:() | | w:foobar | | | +------------------------------------+ ^ | +------------------------------------+ | x:otter | <=== Change x:oh to x:otter here | y:(list of stuff) | | | +------------------------------------+ Error if the variable isn't found. [Do an example with set! and shadowing] ---------------------------------------------------------------------- Two critical phenomena: * METHOD packages up the current environment into a closure - a procedure plus an environment that defines all the names used in the procedure * APPLYing a closure puts a new frame in front of its environment with the parameters, and the evaluates the body ---------------------------------------------------------------------- Rule for building procedures: (method (params) body) builds a procedure object. That consists of: 1. A sequence of variable names -- FORMAL PARAMETERS (or a pointer to it) 2. A code body (or a pointer to it) 3. A pointer to the environment where the procedure was created (i.e., where the METHOD form was evaluated.) We represent it as follows: (method ((x )) (* x x)) __ / \ Params: ((x )) Body: (* x x) \__/ / \ Pointer to the environment. \__/ Note that a procedure object is thus the text of a function expression plus a pointer to the environment with respect to which the function was evaluated. ---------------------------------------------------------------------- Evaluation Rule (Environment Model): To evaluate a compound expression wrt some enviroment, 1. Evaluate the subexpressions wrt that environment 2. Apply the value of the first subexpression to the remaining values. (This is the same as the substitution model except that we mention the environments.) ---------------------------------------------------------------------- Application Rule: (Environment Model): To apply a procedure object to a list of values, 1. Create a new frame, putting it on the "front" (most recent frame) of the environment that is pointed to by the procedure object (recall part (3) of the procedure object) 2. Put bindings for each of the formal parameters (from part 1) to their actual values in the new frame. 3. Evaluate the body (part 2) of the procedure wrt the new environment. (This extends the substitution model by saying how to build the new environment where the body is evaluated; before we just "copied the body with values substituted for names" now we have a more complicated naming mechanism). ---------------------------------------------------------------------- Small example: EVAL[((method ((z )) (+ z 1)) 7), GLOBAL-ENV] EVAL[7, GLOBAL-ENV] ==> 7 EVAL[(method ((z )) (+ z 1)), GLOBAL-ENV] ==> {closure (z) (+ z 1) global-env} APPLY[{closure (z) (+ z 1) global-env}, 7] EVAL[(+ z 1), frame binding z to 7 and pointing to GLOBAL-ENV] Two critical phenomena: * METHOD packages up the current environment into a closure * APPLYing a closure puts a new frame in front of its environment with the parameters, and the evaluates the body ---------------------------------------------------------------------- Big example, with respect to the GLOBAL ENVIRONMENT * That's the one at the Dylan top level (define (make-mult ) (method ((n )) (method ((x )) (* x n)))) The global environment is pretty big: Only one frame, But that frame has all the built-in functions in it: +----------------------------------------+ | + : {add} | | * : {mult} | | ... | | make-mult: ----------------------------+----> {}{} Params: ((n )) | | Body: (method | | ((x ))(* x n)) +----------------------------------------+ >> Env pointer back to global frame Now, if we do (define (double ) (make-mult 2)) we build a new little environemnt: +-----------------------------------+ | 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) >> Points to the environment with n:2 >> Also, enter double:{}{} into the global frame ---------------------------------------------------------------------- Now, let's call (double 7) Make a new frame with +-----------------------------------+ | x : 7 | +-----------------------------------+ which points to n-frame for double. >> Leave all these frames on the board << Evaluate the body (* x n) in this environment. >> We get (* 7 2), which is 14; just follow the lookup rule. ---------------------------------------------------------------------- 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. >> Write the code object -- same code pointer, different environment >> Enter triple into the global environment Each procedure that we create has its *own* *private* environment, with just n in it. * Nobody else sees n ---------------------------------------------------------------------- Introducing set! means that we can no longer simply talk about the equivalence of names and values (because values may change). Key Idea: * Use environment structures as contexts for determining the values of names. * Closer to the implementation than the substitution model.