END OF PART I OF COURSE

Nothing after this will be tested on prelim 1

Review session: tentatively Wed evening (10/6) of next week at 8-9PM.  Sample prelim will be up today, solutions will be up after the review session.


 

NEW TOPIC -- assignment, state variables, the environment model

The *critical* element of the course so far is the substitution model. We're about to step beyond it (Newton to Einstein).2nd third of the course is on the environment model, which we will implement in PS#6.

So far, all of our programs have been FUNCTIONAL PROGRAMS (Well, almost all -- not I/O, define)

They can be thought of as pure mathematical functions.

(foo 10)
==> 18
(foo 10)
==> 18

If foo is a mathematical function, (foo 10) will always return the same thing
* As long as foo hasn't been redefined.


 

Now, we change all that.
New special form: set!

(set! symbol expression)

which CHANGES the value associated with symbol to the value of the expression.

(set! x (bar x))

* symbol is NOT evaluated
* expression IS evaluated

It is illegal to set! a symbol that doesn't exist.

We CREATE variables by
* define
* let, let*, letrec
* parameters to a procedure

set! just CHANGES the values of variables.

(define a 10)
;; a is now 10
(set! a 20)
;; a is now 20
(set! a #t)
;; a is now true

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.

set! is used for its EFFECT, not its VALUE.

  most Scheme expressions set!, print, define
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-under-boulder thing)
  (set! thing (cons thing '(under a boulder)))
  thing)

If we do the substitution model computations on put-under-boulder, 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-under-boulder 'barney)

will (according to the substitution model):
1. Substitute barney for thing everywhere in the body
2. Do something to handle set! -- doesn't really matter what
3. Return the final thing, which is {barney}

So, the model says it will return barney, which is wrong, it returns 

(barney under a boulder)

Note that (BEGIN E1 E2) in a functional language is equivalent to E2, unless [can you guess?] E1 diverges.


 

We need some way to talk about changing values. The substitution model fails: we 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)

(let ((x 'purple)
      (y (set! x 'monster)))
  x)

==> monster

x

==> barney

Since there can be many different variables called x at the same time

Another example not involving set!:

(define x 10)

(define foo (lambda () x))

(let ((x 15)) (foo)) => ???

The issue here is STATIC vs. DYNAMIC SCOPING. The answer is 10, because Scheme  is statically scoped, which means free variables are resolved in the environment of the function DEFINITION (static scoping), not the environment of the function CALL (dynamic scoping).

Scheme's granddaddy LISP was originally dynamically scoped. LISP would return 15 in this case. Most modern programming languages (PASCAL, C) are statically scoped. Current philosophy is that static scoping is a win, since if you are defining a function using a free variable x in the body like this, you usually mean the current value of x in effect where the function is defined, which you know something about, not the value where it is called, which you may not know anything about.


 

The substitution model was based on the idea that you could REPLACE EQUAL EXPRESSIONS WITH EQUAL EXPRESSIONS

If 'state' is defined as 0, you could replace 'state' by '0' and all is well.

This is a concept known as REFERENTIAL TRANSPARENCY.

set! destroys referential transparency -- well really, it changes what expressions are equal in all contexts. Just because 'state' equals '0' in some context, does not mean that that they will continue to be equals in all contexts. Instead, a set! may intervene and change the fact that they are no longer equals. (Again, note the notion of time has been introduced.)


 

We're going to introduce a NEW evaluation model, called the ENVIRONMENT MODEL

An expression only has a value with respect to an ENVIRONMENT

ANALOGY TO REAL LIFE:
* Names (=words) have multiple bindings (=meanings) depending on where you are.

BOOT:


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, no two with the same NAME

where a BINDING is an association of a VALUE to a NAME
- written NAME:VALUE

Here 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 (lowest) frame with a binding for it.
* It is undefined if there aren't any

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 top-level DEFINE 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.


SET! rule:

Evaluating (set! var newval) in an environment
* Look up where the value of `var' lives using lookup rule
* Change that binding to be `newval'

(set! x 'otter)

lowest frame is now

+------------------------------------+
| x:otter                            |
| y:(list of stuff)                  |
+------------------------------------+

Error if the variable isn't found.

Two critical phenomena:

 

 

Rule for building procedures:

(lambda (params) body) builds a procedure object.

That consists of:

1. A sequence of variable names -- FORMAL PARAMETERS

2. A pointer to the environment where the procedure was created (i.e., where the LAMBDA form was evaluated.)

3. A code body

We represent it as follows: [CLOSURE]

(lambda (y) (* y y))

  _

 / \ Params: (y) Body: (* y y)

 \_/

 / \ 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 lambda was evaluated.


Evaluation Rule (Environment Model):

To evaluate a compound expression w.r.t. some enviroment,

1. Evaluate the subexpressions w.r.t 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 containing bindings for each of the formal parameters (from part 1 of closure) to their actual values

2. Put in the new frame on the "front" (most recent frame) of the environment that is pointed to by the procedure object (from part 2 of the closure)

3. Evaluate the body (part 3) of the procedure w.r.t 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).

Note that all three parts of the closure get used (this is a good sanity check). 


Small example:

EVAL[((lambda (z)(+ z 1)) 7), GLOBAL-ENV]

EVAL[7, GLOBAL-ENV] ==> 7

EVAL[(lambda (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:

 

 


Big example, with respect to the GLOBAL ENVIRONMENT

 

(define make-mult
  (lambda (n)
    (lambda (x) (* x n))))

The global environment is pretty big:

Only one frame, but that frame has all the built-in functions in it:

By convention, we will draw this in a double line

+----------------------------------------+
| + : {add}                              |
| * : {mult}                             |
| ...                                    |
| make-mult: ----------------------------+----> {}{} Params: (n) 
|                                        |           Body: (lambda 
|                                        |                  (x)(* x n))
+----------------------------------------+ >> Env pointer to global frame

 

Now, if we do

(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)

>> 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: