390 likes | 399 Vues
Lecture 14: The environment model (cont.) Mutators for compound data ( Section 3.3, pages 251-273) Stack; Queue. Example: explaining make-counter. Counter: something which counts up from a number (define (make-counter n) (lambda () (set! n (+ n 1)) n) )
E N D
Lecture 14:The environment model (cont.)Mutators for compound data (Section 3.3, pages 251-273)Stack; Queue
Example: explaining make-counter • Counter: something which counts up from a number (define (make-counter n) (lambda () (set! n (+ n 1)) n) ) (define ca (make-counter 0))(ca) ==> 1(ca) ==> 2(define cb (make-counter 0))(cb) ==> 1(ca) ==> 3(cb) ==> 2
make-counter: GE ca: E1 p: nb:(lambda () (set! n (+ n 1)) n) p: b:(set! n (+ n 1)) n (define ca (make-counter 0)) | GE n: 0 environment pointerpoints to E1because the lambdawas evaluated in E1 (lambda () (set! n (+ n 1)) n) | E1
make-counter: GE ca: E1 1 n: 0 p: nb:(lambda () (set! n (+ n 1)) n) E2 p: b:(set! n (+ n 1)) n (ca) | GE ==> 1 empty (set! n (+ n 1)) | E2 n | E2 ==> 1
make-counter: GE ca: E1 1 n: 0 p: nb:(lambda () (set! n (+ n 1)) n) 2 E3 p: b:(set! n (+ n 1)) n (ca) | GE ==> 2 empty (set! n (+ n 1)) | E3 n | E3 ==> 2
make-counter: GE cb: ca: E1 E4 n: 2 p: nb:(lambda () (set! n (+ n 1)) n) E3 p: b:(set! n (+ n 1)) n p: b:(set! n (+ n 1)) n (define cb (make-counter 0)) | GE n: 0 (lambda () (set! n (+ n 1)) n) | E4
make-counter: GE ca: cb: E1 E4 n: 2 n: 0 p: nb:(lambda () (set! n (+ n 1)) n) 1 E2 E5 p: b:(set! n (+ n 1)) n p: b:(set! n (+ n 1)) n (cb) | GE ==> 1
make-counter: GE ca: cb: E1 E4 n: 2 n: 1 p: nb:(lambda () (set! n (+ n 1)) n) E2 p: b:(set! n (+ n 1)) n p: b:(set! n (+ n 1)) n Capturing state in local frames & procedures
Lessons from the make-counter example • Environment diagrams get complicated very quickly • Rules are meant for the computer to follow, not to help humans • A lambda inside a procedure body captures theframe that was active when the lambda was evaluated • this effect can be used to store local state
Lets do a message passing example (define (cons x y) (define (dispatch op) (cond ((eq? op 'car) x) ((eq? op 'cdr) y) (else (error "Unknown op -- CONS" op)))) dispatch) (define (car x) (x 'car)) (define (cdr x) (x 'cdr)) (define a (cons 1 2)) (car a)
cons: car: cdr: p: x,y b:(def disp (op) .. dispatch) p: x b:(x ‘car) p: x b:(x ‘cdr) (define (cons x y) ..) | GE GE
cons: car: p: x,y b:(def disp (op) .. dispatch) Disp: p: x b:(x ‘car) p: op b:(cond ..) (define a (cons 1 2)) | GE GE cdr: a: x: 1 Y: 2 E1 p: x b:(x ‘cdr)
cons: car: x: p: x,y b:(def disp (op) .. dispatch) E2 Disp: p: x b:(x ‘car) p: op b:(cond ..) (car a) | GE GE cdr: a: x: 1 Y: 2 E1 p: x b:(x ‘cdr)
cons: car: p: x,y b:(def disp (op) .. dispatch) Disp: p: x b:(x ‘car) empty E3 p: op b:(cond ..) (x ‘car) | E2 GE cdr: a: x: x: 1 Y: 2 E1 E2 p: x b:(x ‘cdr)
cons: car: p: x,y b:(def disp (op) .. dispatch) Disp: p: x b:(x ‘car) p: op b:(cond ..) Returned value is x|E1=1.After that what remains is: GE cdr: a: x: 1 Y: 2 E1 p: x b:(x ‘cdr)
Explaining Nested Definitions • Nested definitions : block structure (define (sqrt x) (define (good-enough? guess) (< (abs (- (square guess) x)) 0.001)) (define (improve guess) (average guess (/ x guess))) (define (sqrt-iter guess) (if (good-enough? guess) guess (sqrt-iter (improve guess)))) (sqrt-iter 1.0))
sqrt: GE E1 x: 2 good-enough: improve: sqrt-iter: p: xb:(define good-enou ..) (define improve ..) (define sqrt-iter ..) (sqrt-iter 1.0) p: guessb:(< (abs ….) guess: 1 sqrt-iter guess: 1 The same x in all subprocedures good-enou? (sqrt 2) | GE
Summary: The Environment Model • A precise, completely mechanical description of: • name-rule looking up the value of a variable • define-rule creating a new definition of a var • set!-rule changing the value of a variable • lambda-rule creating a procedure • application rule applying a procedure • You will use it to implement the Scheme interpreter, • Gotta practice using it… • It’s a major part of the final exam
Mutating Compound Data • constructor: (cons x y) creates a new pair p • selectors: (car p) returns car part of pair (cdr p) returns cdr part of pair • mutators: (set-car! p new-x) changes car pointer in pair (set-cdr! p new-y) changes cdr pointer in pair ; Pair,anytype -> undef -- side-effect only!
a X b red blue 10 Example: Pair/List Mutation (define a (list ‘red ‘blue)) (red blue) a ==> (define b a) b ==> (red blue) (set-car! a 10) a ==> (10 ‘blue) b ==> (10 ‘blue)
d c X red blue 10 red blue Compare this with… (define c (list ‘red ‘blue)) (define d (list ‘red ‘blue)) c ==> (red blue) d ==> (red blue) (set-car! c 10) c ==> (10 blue) d ==> (red blue)
c a d X X b red red blue blue 10 10 red blue Equivalent vs. The Same In the first example a and b are the same. A change to one changes the other. The point to a single object. In the second example c and d have at first the same value, but later on one changes and the other does not.They just happen to have at some point in time the same value.
Eq? vs. Equal? • To check whether two names point to the same object: Test witheq? (eq? a b) ==> #t • To check whether two elements currently have the same content: Test withequal? (equal? (list 1 2) (list 1 2)) ==> #t(eq? (list 1 2) (list 1 2)) ==> #f
Q&A: “2 B 2 B =” Q: If we change an object, is it the same object? A: Yes, if we retain the same pointer to the object. Q: How can we tell if two elements share the same object? A. One thing you can do is mutate one and see if the other also changes.
x a b 1 2 Example 2: Pair/List Mutation (define x (list 'a 'b)) (set-car! (cdr x) (list 1 2)) • Eval (cdr x) to get a pair object • Change car pointer of that pair object (a ( 1 2))
x X 3 4 y X X 7 1 2 Your Turn x ==> (3 4) y ==> (1 2) (set-car! x y) x ==> (set-cdr! y (cdr x)) x ==> (set-cdr! x (list 7) x ==> ((1 2) 4) ((1 4) 4) ((1 4) 7)
Summary • Scheme provides built-in mutators • set! to change a binding • set-car! and set-cdr! to change a pair • Mutation introduces substantial complexity • Unexpected side effects • Substitution model is no longer sufficient to explain behavior
Stack Data Abstraction • constructor:(make-stack) returns an empty stack • selectors:(top stack) returns current top element from a stack • operations:(insert stack elem) returns a new stack with the element added to the top of the stack (delete stack) returns a new stack with the top element removed from the stack (empty-stack? stack) returns #t if no elements, #f otherwise
Insert Insert Insert Stack Data Abstraction Last in, First out. Insert Delete
Stack Contract • If s is a stack, created by (make-stack)and subsequent stack procedures, where i is the number of insertions and d is the number of deletions, then • If d>i then it is an error • If d=i then (empty-stack? s) is true, and (top s) and (delete s) are errors. • If d<i then (empty-stack? s) is false and(top (delete (insert s val))) = (top s) • If d<=i then (top (insert s val)) = val for any val
Stack Implementation Strategy • implement a stack as a list a b d • we will insert and delete items at the front of the stack
Stack Implementation (define (make-stack) nil) (define (empty-stack? stack) (null? stack)) (define (insert stack elem) (cons elem stack)) (define (delete stack) (if (empty-stack? stack) (error "stack underflow – delete") (cdr stack))) (define (top stack) (if (empty-stack? stack) (error "stack underflow – top") (car stack)))
Limitations in our Stack • Stack does not have identity (define s (make-stack)) s ==> () (insert s 'a) ==> (a) s ==> () (set! s (insert s 'b)) s ==> (b)
(delete! s) s X d c a stack Alternative Stack Implementation • The data type contains a list of elements – as before. • Insert and delete mutate a stack object. • The first element of the list is special to distinguish Stack objects from other lists – defensive programming The stack will be a mutable data type.
(define (make-stack) (cons 'stack nil)) Alternative Stack Implementation (1) (define (stack? stack) (and (pair? stack) (eq? 'stack (car stack)))) (define (empty-stack? stack) (if (not (stack? stack)) (error "object not a stack:" stack) (null? (cdr stack)))) (define (top stack) (if (empty-stack? stack) (error "stack underflow – top") (cadr stack)))
Alternative Stack Implementation (2) (define (insert! stack elem) (cond ((not (stack? stack)) (error "object not a stack:" stack)) (else (set-cdr! stack (cons elem (cdr stack))) stack))) (define (delete! stack) (if (empty-stack? stack) (error "stack underflow – delete") (set-cdr! stack (cddr stack))) stack)
Does the user have to know about it? Usually the user does not care about the implementation As long as the contract is being kept. Both implementations keep the contract. Yet: This is a change to the abstraction! The user should know if the object mutates or not in order to use the abstraction correctly. If we (define a b), can a later change because of changes to b?