Note: PS1 concentrates on generalizing existing code.

Program by example: read the code we give you, copy and modify!

Sections #1 and #2 are huge, #3 and #4 are nearly empty.  Please consider transferring to sections #3 or #4. Section #3 is MW 11:15-12:05, Section #4 is MW 1:25-2:15

Last demo session today, B7 Upson, 7:00 and 7:30.

Today's topics:

We've seen (in recitation):

(operator operand1 ... operandn)

eval each subform, then apply operator's value to operands' values.

Example: (/ (* 4 3) (+ 2 1)) ==> 4

So far we have hand waved a lot in understanding the evaluation process -- determining the value of Scheme expressions

Today: Substitution Model -- formal means of understanding Scheme expressions (at least ones without assignment, which we won't use for quite a while). 

Most critical thing in first half of CS212!  One of only two things I personally recommend that you memorize.


A lambda

In some weird way, (* x x) is squaring. Except that this doesn't quite make sense.

Similarly averaging is (/ (+ x y) 2)

If you want to package squaring or averaging for later use, you need to

Squaring involves one variable, x.

To package it up in Scheme, we use the special form called LAMBDA (does not follow the normal evaluation rule)

- LAMBDA makes a procedure

(lambda (x) (* x x))

Contains:

Or, read it as:

NOTE:  the lambda form by itself does NO COMPUTATION!  (Students always get this wrong...)

It creates an OBJECT which CAN do the computation when used in first position in a combination.

Computation happens when it is APPLIED (applied means used in first position in a combination)

[Sort of like the difference between a recipe to make spaghetti and actual spaghetti.  Don't try to eat the recipe!]

Example:

(/ 1 0) produces an error

(lambda () (/ 1 0)) is not an error (yet.) It is a procedure of no arguments!

what makes the second one of these produce an error??

((lambda () (/ 1 0))) when it is applied (used)

NOTE: creating a procedure (using LAMBDA) is completely separate from naming it (using DEFINE)

Scheme != C


[We also contrast the following --- use if there are questions]

(define obi 3)

(define darth (lambda () 3))

obi evaluates to 3

darth evaluates to however Scheme prints out a procedure object

(obi) generates an error, application of a non-procedure object, 3

(darth) evaluates to 3

If you understand these examples, then you are a long way towards understanding evaluation of simple Scheme expressions.


Mantra: Everything in Scheme has a value; everything is an expression.

[REPEAT THIS A FEW TIMES]

1's Value is 1

lambda returns a PROCEDURE OBJECT as its value:

Anything you can do with all other values, you can do with procedures (one of the cool things about Scheme!)

By the evaluation rule (from section), we know how to use a procedure object:

((lambda (x) (* x x)) 5) ==> 25

versus

(define square (lambda (x) (* x x)))
(square 5)

has the same meaning! We will get more precise about this in a moment.

Now we can use square conveniently too.

(square 5) ==> 25
(square 3) ==> 9
(+ (square 2) (square 3)) ==> 13

*** NOTE: We can now use square just as if it were a primitive operation, like + - * / etc.


*** Clean the board here ***

*** And write small ***

 

Now, we have progressed to the point where we can try implementing Heron of Alexandria's square root algorithm from last time:

(define heron-sqrt
  (lambda (x)
    ;Try some guess for sqrt(x)--we always pick 1.
    (try 1 x)))

(define try
  (lambda (guess x)
   ;If the guess is good enough
   (if (good-enough? guess x) 
       ;then stop and return the old guess
       guess
       ;otherwise, try an improved guess.
       (try (improve guess x) x))))

(define good-enough?
  (lambda (guess x)
    (< (abs (- (square guess) x)) 0.0001)))

(define improve
  (lambda (guess x)
    (/ (+ guess (/ x guess)) 2.0)))

(define square
  (lambda (x) (* x x)))

*** Leave the code on the board! ***

 

Note that try is a recursive definition, it calls itself.

Paid commercial advertisement: There's a difference between recursive DEFINITIONS (like try) and recursive processes. That's for another lecture though.


 

We've snuck in a new beast:

(if test consequent alternate)

is a special form and doesn't follow the usual evaluation rule (which is, evaluate all the subexpressions, then apply the first value - the operator - to the remaining values - the arguments).

IF's special rule is:

1. Evaluate the test

2. If the test is true, evaluate and return the consequent

3. If the test is false, evaluate and return the alternate.

It only evaluates one arm.

What's true?

#t is true

#f is false

In fact, almost anything is true:

#f is the only false value!

(if #t A B) evaluates to A

What is (if 0 10 20)? 10, of course!

(C can be cured)

MORE ON THIS AND RELATED FORMS IN RECITATION.


This is all we need to write that sqrt procedure!


From this you can sort of see the processing in sqrt:

*** Go through this out loud, quickly ***

(sqrt 2)

calls

(try 1 2)

calls

(good-enough? 1 2) which returns false,

so do

(try (improve 1 2) 2)

and (improve 1 2)

calls

(average 1 2)

which returns 3/2,

so we call (try 3/2 2) as the new guess.

This is all a little fuzzy; we can be considerably more precise about (and get a better understanding of) exactly what Scheme expressions do.

A model of the evaluation process:

evaluation - reducing an expression to a value (like 4, for instance)

Like all models (and much of 212) it's a bit of a lie...


We use Revised/Recursive Substitution Model, RSM, covered in the handout on the web (available today). [Get used to checking the web page!]

It is a MODEL:

- A tool to help us understand the values that our procedures are computing (NOT WHAT THE COMPUTER DOES!)

- It helps us reason about programs.

[This is one of the few things in 212 you should probably memorize!]

There are two basic rules of the model:

1. To evaluate a combination (other than a special form), evaluate the subexpressions of the combination, then apply the procedure object that is the value of the leftmost subexpression to the remaining values.

2. To apply a compound procedure to a set of arguments, evaluate the body of the procedure with each argument SUBSTITUTED for the corresponding parameter.

Notation:

(...) means evaluation, Rule 1

[...] means application, Rule 2.

 

We'll draw a square around any object that has already been evaluated, to distinguish them from symbols that must be evaluated:

*** Use braces here to denote squares ***

5 is a symbol

{5} is some computer representation of the number 5.

* is a symbol

{mult} is its value, the multiplication function.

The symbol and the computer representation are DISTINCT entities.

Procedure objects need notation:

{proc (params) body}

is the value of (lambda (params) body)

NOTE: proc is not part of Scheme (any more than mult is).

(lambda (x) (+ x 5)) evaluates to {proc (x)  (+ x 5)}


Let's now evaluate (square 5) where square is defined as above.

Now rule 1 says, apply {proc (x) (* x x)} to {5}:

[{proc (x) (* x x)} {5}]

by rule 2, this gives us the body of the proc --- (* x x) --- with x replaced by {5}:

(* {5} {5})

`*' evaluates to {mult}

[{mult} {5} {5}]

We're now beyond the substitution model's rules, because {mult} and {5} are primitives. All that the substitution model does is tell us how to reduce expressions to values, these are all values.

Each operation in some sense has its own special-case rules, in this case mult simply performs multiplication, resulting in {25}


Here's a more complicated example:

1. (sqrt 2)

2. [ {proc (x) (try 1 x)} {2} ]

3. (try 1 {2})

4. [ {proc (guess x) ...} {1} {2} ]

*** If is a special form, so we handle it specially (has its own rule in the model):

5. (if (good-enough? {1} {2})
       {1}
       (try (improve {1} {2}) {2}))

By the rule for if, we first evaluate the test:

[ {proc (guess x) ...} {1} {2} ]

(< (abs (- (square {1})) {2}) 0.01)

...

#f

Now by the special rule for IF we evaluate the alternative because the test was false:

6. (try (improve {1} {2}) {2})

We have to evaluate this "inside out" because in order to evaluate all the subexpressions we need a value for the second subexpression which is itself a combination. We get this value by

[ {proc (guess x) ...} {1} {2} ]

(average {1} (/ {1} {2}))

[{proc (a b) (/ (+ a b) 2)} {1} {2}]

(/ (+ {1} {2}) 2)

...

{1.5}

Now we have evaluated each subexpression of (try (improve {1} {2}) {2}) resulting in

[ {proc (guess x) ...} {1.5} {2}]

Substituting the body of the procedure with values {1.5} and {2}

We're back at something that looks like step 4.

The pattern repeats.

7. 

 (if (good-enough? {1.5} {2})
       {1.5}
       (try (improve {1.5} {2}) {2}))

The test is #f so by reasoning analogous to above,

(try (improve {1.5}{2}) {2}) evaluates to {1.416667}

8. [ {proc (guess x) ...} {1.416667} {2} ]

9. 

 (if (good-enough? {1.416667} {2})
       {1.416667}
       (try (improve {1.416667} {2}) {2}))

The test of the if evaluates to #t in this case so the value is thus {1.416667}

 

With the Substitution Model, we have a very precise model for determining the value of expressions....

 

AT LEAST, FOR NOW. It breaks when we add assignment, but that won't happen for quite a while. We'll write some complicated programs without it. That's part of the lesson: you don't need to depend on assignment as much as you do. Kick the habit.

 

----------------------------------------------------------------------

Important points in today's lecture: