; CSE 341, Fall 2011, Lecture 15 ; Mutation, Pairs, Thunks, Laziness, Streams, Memoization #lang racket (define f (λ (x) (+ x (* x b)))) ; forward reference okay (define b 3) (define c (+ b 4)) ; you could /mutate/ an earlier binding to be something different, but ; that /changes/ what the value binding has, even for other earlier ; bindings, so this is "evil" and to be avoided especially at top-level (set! b 5) (define z (f 4)) ; set! affects z (define w c) ; set! does not affect w ; way more "evil": if code outside the file (module) could ; set! a top-level binding ; in Scheme: yes ; in Racket: a strange compromise: can set! outside only if there is a ; set! of that binding inside ; try in REPL: (set! w 4) (set! b 4) ; if this evil /could/ happen our function f could defend itself by ; making a copy of b when it was created: (define f-weird-and-safe (let ([b b]) ; would need to be /before/ the (set! b ...) (λ (x) (+ x (* x b))))) ; but that wouldn't quite be good enough since + and * are just functions: (define yep (and (procedure? +) (procedure? *))) ; Racket won't let us set! them, but if it did, "safe" f would require: (define f-really-weird-and-safe (let ([b b] ; would need to be before any mutations [+ +] [* *]) (λ (x) (+ x (* x b))))) ; and if f used helper functions, they would need to be safe too ; moral: if mutation is possible, the only defense is to make a copy ; of everything and use only your local copies ; (thankfully in Racket, don't have to worry) ;; the truth about cons/car/cdr: it's just pairs (define pr (cons 1 (cons #t "hi"))) (define hi (cdr (cdr pr))) (define no (list? pr)) (define yes (pair? pr)) ; (define do-not-do-this (length pr)) (define xs (cons 1 (cons #t (cons "hi" null)))) (define of-course (list? xs)) (define hi-again (car (cdr (cdr xs)))) (define hi-again-shorter (caddr xs)) ;; Unlike in Scheme, Racket cons cells are immutable ;; * for all the usual great reasons like ignoring aliasing ;; * and the built-in list? need not traverse the list ;; but since mutable pairs are useful, Racket has them too: ;; mcons, mcar, mcdr, set-mcar!, set-mcdr! ;; run-time error to use mcar on a cons or car on an mcons (define mpr (mcons 1 (mcons #t "hi"))) (set-mcdr! (mcdr mpr) "bye") (define bye (mcdr (mcdr mpr))) ;; delayed evaluation (define (factorial x) (if (= x 0) 1 (* x (factorial (- x 1))))) (define (my-if-bad e1 e2 e3) (if e1 e2 e3)) (define (factorial2 x) (my-if-bad (= x 0) 1 (* x (factorial2 (- x 1))))) (define (my-if-good e1 e2 e3) (if e1 (e2) (e3))) (define (factorial3 x) (my-if-good (= x 0) (λ () 1) (λ () (* x (factorial3 (- x 1)))))) ; an infinite loop (when called) that does not type-check in ML (define (loop-thunk) ((lambda (x) (x x)) (lambda (x) (x x)))) ;; thunks for delayed evaluation for performance ;; (or termination) (define (slow-add x y) (letrec ([slow-id (lambda (y z) (if (= 0 z) y (slow-id y (- z 1))))]) (+ (slow-id x 50000000) y))) (define (my-mult x y-thunk) ;; assumes x is >= 0 (cond [(= x 0) 0] [(= x 1) (y-thunk)] [#t (+ (y-thunk) (my-mult (- x 1) y-thunk))])) ;(my-mult 0 (lambda () (slow-add 3 4))) ;(my-mult 1 (lambda () (slow-add 3 4))) ;(my-mult 2 (lambda () (slow-add 3 4))) ;(my-mult 0 (let ([x (slow-add 3 4)]) (lambda () x))) ;(my-mult 1 (let ([x (slow-add 3 4)]) (lambda () x))) ;(my-mult 2 (let ([x (slow-add 3 4)]) (lambda () x))) ;; memoization (purity key) (define (my-delay th) (mcons #f th)) ;; a one-of "type" we will update /in place/ (define (my-force p) (if (mcar p) (mcdr p) (begin (set-mcar! p #t) (set-mcdr! p ((mcdr p))) (mcdr p)))) ;; for my-mult ;(my-mult 0 (let ([x (my-delay (λ () (slow-add 3 4)))]) (λ () (my-force x)))) ;(my-mult 1 (let ([x (my-delay (λ () (slow-add 3 4)))]) (λ () (my-force x)))) ;(my-mult 2 (let ([x (my-delay (λ () (slow-add 3 4)))]) (λ () (my-force x)))) ;; if we rewrite my-mult to expect a promise (something produced by ;; my-delay) then it is easier to use: (define (my-mult-promise x y-promise) (cond [(= x 0) 0] [(= x 1) (my-force y-promise)] [#t (+ (my-force y-promise) (my-mult-promise (- x 1) y-promise))])) ;(my-mult-promise 2 (my-delay (lambda () (slow-add 3 4)))) ;; streams -- (a.k.a. infinite lists) ;(define ones-really-bad (cons 1 ones-really-bad)) (define ones-bad (lambda () (cons 1 (ones-bad)))) (define ones (lambda () (cons 1 ones))) (define nats (letrec ([f (lambda (x) (cons x (lambda () (f (+ x 1)))))]) (lambda () (f 1)))) (define powers-of-two (letrec ([f (lambda (x) (cons x (lambda () (f (* x 2)))))]) (lambda () (f 2)))) (define (stream-maker fn arg) (letrec ([f (lambda (x) (cons x (lambda () (f (fn x arg)))))]) (lambda () (f arg)))) (define nats2 (stream-maker + 1)) (define powers2 (stream-maker * 2)) (define (number-until stream tester) (letrec ([f (lambda (stream ans) (let ([pr (stream)]) (if (tester (car pr)) ans (f (cdr pr) (+ ans 1)))))]) (f stream 1))) ; (number-until powers2 (lambda (x) (= x 16))) ;; for fibonacci (define (fibonacci1 x) (if (or (= x 1) (= x 2)) 1 (+ (fibonacci1 (- x 1)) (fibonacci1 (- x 2))))) (define (fibonacci2 x) (letrec ([f (lambda (acc1 acc2 y) (if (= y x) (+ acc1 acc2) (f (+ acc1 acc2) acc1 (+ y 1))))]) (if (or (= x 1) (= x 2)) 1 (f 1 1 3)))) (define fibonacci3 (letrec([memo null] [f (lambda (x) (let ([ans (assoc x memo)]) (if ans (cdr ans) (let ([new-ans (if (or (= x 1) (= x 2)) 1 (+ (f (- x 1)) (f (- x 2))))]) (begin (set! memo (cons (cons x new-ans) memo)) new-ans)))))]) f))