CS 227 Lecture -*- Outline -*- * Assignment and State (11.2) We've seen vectors, which can change state. In chapter 11 we look at changing the state of variables (set!) and cons-cells (set-car! and set-cdr!). ** Assignment (set!) The Scheme keyword set! allows us to change the value to which a variable is bound. Like := in Pascal, etc. Play with this on the computer. (define i 0) i (set! i (add1 i)) i (set! i 100) i uses: sometimes allows better communication between program parts, -- instead of computing a variable's value all at once, it can be computed incrementally. -- meshes well with iteration. *** Syntax: ------------------ ASSIGNMENT STATEMENT Syntax: (set! var value) Examples: (set! i (add1 i)) (set! ls (cdr ls)) (set! i (+ 3 4)) ------------------ var is a variable name value is any expression Note that set! is not a procedure, as the variable is not evaluated. draw picture of what this does i ----> [4] after (set! i 9999) this becomes: i ----> [9999] *** Semantics -- there is no value returned, set! is purely a side effect. -- the variable must be already bound. **** Errors (set! not-bound 2) **** Scope try the following (let ((x 2)) (writeln x) (let ((x 3)) (writeln x) (set! x 'inside) (writeln x)) (writeln x)) (letrec ((du (lambda (x) (writeln x) (if (not (zero? x)) (begin (set! x (sub1 x)) (du x) (writeln x)))))) (du 4)) Note how each recursive call has its own copy of the variables, set only affects the local one. *** Set! vs. Define Try the following (on PC Scheme): (set! foo 3) ; error, foo not defined, ; use C-q to exit PC Scheme inspector ; (but works in Chez Scheme, sigh) (define foo 3) foo (set! foo 3) (let ((bar 4)) (writeln foo) (define foo 5) ; illegal syntax! (writeln foo)) (let ((bar 4)) (define foo 5) ; legal, but not very portable syntax (writeln foo)) foo (let ((bar 4)) (writeln foo) (set! foo 5) (writeln foo)) foo might what to draw pictures for these -------------------- DEFINE! vs. SET define establishes variable bindings. set! changes variable bindings. define should not change bindings set! cannot establish bindings set! changes closest surrounding binding. --------------------- Set! cannot be (portably) used unless the variable has already been bound (with let, define, lambda, letrec) Define should not be used to change bindings -- can do that at the top level, but that is a convenience for fixing bugs. (actually, it will also do that at other levels too) Set! changes the closest surrounding lexical binding Define establishes a new binding, but does not change one in a surrounding environment. *** Examples **** straight-line the following is an optimization (improvement for speed) of the standard square root procedure (loop unrolling) ------------- (define fast-sqrt ; TYPE: (-> (number) number) (lambda (a) ; REQUIRES: a > 0 ; ENSURES: result is an approx ; to the square root of a (let ((u 1.0) (improve (lambda (u) (/ (+ u (/ a u)) 2)))) (set! u (improve u)) (set! u (improve u)) (set! u (improve u)) u))) ------------- *** iteration and while loops We've been writing looping constructs like the following. -------------------------- ;; compare - Program 9.5, pg. 272 - (define vector-stretch-imp ; TYPE: (-> (vector integer) vector) (lambda (vec new-size) ; ENSURES: result is a new copy of vec ; but having size new-size, ; result filled with () if bigger (let ((size (vector-length vec)) (res (make-vector new-size))) (letrec ((copy! ;TYPE: (-> (natural) void) (lambda (i) ; REQUIRES: i <= new-size ; and for all 0 <= j < i, ; the jth element of res is ; correctly filled in (if (< i new-size) (begin (if (< i size) (vector-set! res i (vector-ref vec i)) (vector-set! res i '())) (copy! (add1 i))))))) (copy! 0) res)))) -------------------------- Using set!, we can declare i as a local variable outside the loop, initialize it when it is named in a let, and mutate it (assign to it) -------------------------- ;; compare - Program 9.5, pg. 272 - (define vector-stretch ; TYPE: (-> (vector integer) vector) (lambda (vec new-size) ; ENSURES: result is a new copy of vec ; but having size new-size, ; result filled with () if bigger (let ((size (vector-length vec)) (res (make-vector new-size)) (i 0)) (letrec ((loop! ; TYPE: (-> () void) (lambda () ; REQUIRES: i <= new-size ; and for all 0 <= j < i, ; the jth element of res is ; correctly filled in (if (< i new-size) (begin (if (< i size) (vector-set! res i (vector-ref vec i)) (vector-set! res i '())) (set! i (add1 i)) (loop!)))))) (loop!) res)))) ----------------------------- Note the declaration and use of set! for i, and that the loop is a thunk. (set! i (add1 i)) is exactly analogus to the recursive call (copy! (add1 i)) in the -imp version (without set!) Another example ---------------------- ;; compare - Program 9.7, pg. 272 - (define vector-update ;TYPE:(-> (vector integer datum) vector) (lambda (vec k val) ; ENSURES: result is a new vector that ; has the same elements as vec, but ; with the the kth element replaced by ; val (let ((size (vector-length vec))) (let ((res (make-vector size)) (i 0)) (letrec ((loop! (lambda () ; REQUIRES: i <= size ; and for all 0 <= j < i ; the jth element of res is ; (vector-ref vec i) unless ; j=k, and then it's val (if (< i size) (begin (if (= i k) (vector-set! res i val) (vector-set! res i (vector-ref vec i))) (set! i (add1 i)) (loop!)))))) (loop!) res))))) ----------------------- Now that we've seen two examples of this we can abstract out the common code in a way that is different than vector-generator, and more like conventional languages. ----------------------- ; compare Program 11.8, pg. 353 - (define while-proc ; TYPE: (-> ((-> () boolean) ; (-> () void)) ; void) (lambda (pred-th body-th) ; EFFECT: do the pred-th, if it's true ; do the body-th; repeat until ; the pred-th is false. (letrec ((loop ; TYPE: (-> () void) (lambda () (if (pred-th) (begin (body-th) (loop)))))) (loop)))) ----------------------- a thunk is a parameterless procedure The construct in conventional languages is called while; it's like this but doesn't require the use of lambda to make thunks (There is a macro version of while in exercise 14.10) ; NOTE: presumably the body-th changes something ; that the pred-th checks Examples written with while-proc. ----------------------- ;; compare - Program 9.5, pg. 272 - (define vector-stretch-while ; TYPE: (-> (vector integer) vector) (lambda (vec new-size) ; ENSURES: result is a new copy of vec ; but having size new-size, ; result filled with () if bigger (let ((size (vector-length vec)) (res (make-vector new-size)) (i 0)) (while-proc (lambda () (< i new-size)) ; INVARIANT: i <= new-size ; and for all 0 <= j < i, ; the jth element of res is ; correctly filled in (lambda () (if (< i size) (vector-set! res i (vector-ref vec i)) (vector-set! res i '())) (set! i (add1 i)))) res))) ------------------------- Note that the requires clause turns into a "loop invariant" comment ------------------------- FOR YOU TO DO (vector-update-while '#(a b c d) 2 'new) ==> #(a b new d) Use while-proc to write this. ------------------------- ;; compare - Program 9.7, pg. 272 - (define vector-update-while ;TYPE:(-> (vector integer datum) vector) (lambda (vec k val) ; ENSURES: result is a new vector that ; has the same elements as vec, but ; with the the kth element replaced by ; val (let ((size (vector-length vec))) (let ((res (make-vector size)) (i 0)) (while-proc (lambda () (< i size)) ; INVARIANT: i <= size ; and for all 0 <= j < i ; the jth element of res is ; (vector-ref vec i) unless ; j=k, and then it's val (lambda () (if (= i k) (vector-set! res i val) (vector-set! res i (vector-ref vec i))) (set! i (add1 i)))) res)))) Q: do you see how these were derived from the original programs? Q: How does this compare with vector-generator? More examples can be had by writing imperative versions of: - Program 9.9, pg. 273 - - Program 9.11, pg. 274 - - Program 9.13, pg. 275 - ** set! vs. vector-set! *** can use 1 element vectors to act like variables (omit) ------------------------- VECTORS SET! and VARS (define v (vector 0)) (define v 0) (define set!-v (lambda (val) (vector-set! v 0 val))) (define get-v (lambda () (vector-ref v 0))) (set!-v 99) (set! v 99) (get-v) v ------------------------- *** setting a variable and mutating a vector are different consider the following transcript. ------------------ > (define va (vector 1 2 3)) > (define vb va) > (vector-set! vb 1 99) > va #(1 99 3) > vb #(1 99 3) > (set! va (vector 22 33 44)) > va #(22 33 44) > vb #(1 99 3) ----------------- ** Stacks Explain analogy to lists and to a cafeteria stack of plates or stack of papers. LIFO --- last in first out. ------------------- ; - Program 11.1, pg. 344 - (define stk '()) ; TYPE: stack (define empty? ; TYPE:(-> () boolean) (lambda () (null? stk))) (define top ; TYPE: (-> () datum) (lambda () (if (empty?) (error "top: The stack is empty.") (car stk)))) (define print-stack ; TYPE: (-> () void) (lambda () (display "TOP: ") (for-each (lambda (x) (display x) (display " ")) stk) (newline))) (define push! ; TYPE: (-> (datum) void) (lambda (a) (set! stk (cons a stk)))) (define pop! ; TYPE: (-> () void) (lambda () (if (empty?) (error "pop!: The stack is empty") (set! stk (cdr stk))))) ------------------- Play with this. This starts to be like Object-oriented programming (more in CH 12). ** Memoizing (Caching) Something we couldn't do (easily) without set! This application is using mutation to save results in the computer memory between computations to improve efficiency. ------------------ MEMOIZING Problem: How to avoid computing the value of a procedure (like Fibbonacci) each time it is called. Without bothering the caller! Solution: memoize (remember) the results, and only call the procedure if necessary. ------------------- *** Data Structure How to remember the results? Cannot write them down (3 10 15) (like joke: some football scores 10, 17, 8,...) Need to remember the function argument and the result for it. Table of arguments vs. Results Q: Who keeps track of that? the clients could, but discuss how painful that would be so need to use set!, which is like an implicit argument. Q: How would you represent that? Could use either proper or improper lists. Both have the key (input argument) as car, but the result is in a different place. We abstract out this decision with a procedure parameter. ------------------- ; - Program 11.2, pg. 346 - (define lookup ; TYPE: (-> (datum table ; (-> (datum) datum) ; (-> () datum)) ; datum) (lambda (obj table success-proc failure-proc) ; ENSURES: if obj has an association ; in table, then result is the result ; of applying success-proc to its ; value, otherwise result is ; the result of (failure-proc) (letrec ((lookup ;TYPE: (-> (table) datum) (lambda (table) (if (null? table) (failure-proc) (let ((pr (car table))) (if (equal? (car pr) obj) (success-proc pr) (lookup (cdr table)))))))) (lookup table)))) ------------------- Note that what to do in case of errors is also abstracted out. With lookup written, we can write memoize that takes a procedure as an argument and returns a procedure that does the same thing; but faster when repeated calls are made. ------------------- ; - Program 11.4, pg. 347 - (define memoize ; TYPE: (-> ((-> (datum) datum)) ; (-> (datum) datum)) (lambda (proc) ; ENSURES: result is proc, ; but faster as result is looked up ; if the argument has been seen before (let ((table '())) (lambda (arg) (lookup arg table (lambda (pr) (cdr pr)) (lambda () (let ((val (proc arg))) (set! table (cons (cons arg val) table)) val))))))) ------------------- Note: table is local to memoize For example, let fib be ------------- (define fib (lambda (n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))) -------------- Then can memoize this in 2 ways: (define fib-m (memoize fib)) *draw* a picture of this Call fib-m on various arguments. (Up to 30) But fib-m does not use memoization to improve the recursive calls. That is, the recursion goes through fib, not fib-m. So the following is better. ------------------- ; - Program 11.5, pg. 348 - (define memo-fib ; TYPE: (-> (number) number) (memoize (lambda (n) (if (< n 2) n (+ (memo-fib (- n 1)) (memo-fib (- n 2))))) )) ------------------- *draw* a picture of this too. Show how this allows us to compute memo-fib of 100, even though fib-m takes a long time for 25. *** Recap say why we needed set! to do this. ** lambda, the ultimate goto (Basic Programming) (can skip) History of while loop ------------------ LAMBDA THE ULTIMATE GOTO (define member? (lambda (item ls) (cond ((null? ls) #f) ((equal? (car ls) item) #t) (else (member? item (cdr ls)))))) ; - Program 11.7, pg. 352 - (define member? (lambda (item ls) (let ((goto ; TYPE: (-> ((-> () void)) ; void) (lambda (label) (label)))) (letrec ((start (lambda () (cond ((null? ls) #f) ((equal? (car ls) item) #t) (else (goto reduce))))) (reduce (lambda () (set! ls (cdr ls)) (goto start)))) (goto start))))) ------------------- -- This style of programming often is used in assembly language -- Amazing that it can be used in Scheme! -- But this "sphagetti code" carried further makes programs hard to understand. Most imperative programmers use a more "structured" style, with only forward jumps; one entry, one exit. This is why one prefers things like the while loop Improved version of member? --------------------------------- STRUCTURED VERSION OF member? (define member? (lambda (item ls) (let ((ans #f)) (while-proc (lambda () (not (or (null? ls) ans))) (lambda () (if (equal? (car ls) item) (set! ans #t) (set! ls (cdr ls))))) ans))) ----------------------------------