#lang racket (require racket/mpair) ;;;;;;;;;;;;;;;; ;; CONNECTORS ;; ;;;;;;;;;;;;;;;; (define (make-connector) (define value false) (define informant false) (define constraints empty) (define (set-my-value newval setter) (cond [(not (has-value? me)) (set! value newval) (set! informant setter) (for-each-except setter inform-about-value constraints)] [(not (= value newval)) (error "Contradiction" (list value newval))] [else 'ignored])) (define (forget-my-value retractor) (cond [(eq? retractor informant) (set! informant false) (for-each-except retractor inform-about-no-value constraints)] [else 'ignored])) (define (connect new-constraint) (when (not (memq new-constraint constraints)) (set! constraints (cons new-constraint constraints))) (when (has-value? me) (inform-about-value new-constraint) 'done)) (define (me request) (cond [(eq? request 'has-value?) (if informant true false)] [(eq? request 'value) value] [(eq? request 'set-value!) set-my-value] [(eq? request 'forget) forget-my-value] [(eq? request 'connect) connect] [else (error "Unknown operation -- CONNECTOR" request)])) me) (define (for-each-except exception procedure lst) (map procedure (remove exception lst)) 'done) (define (has-value? connector) (connector 'has-value?)) (define (get-value connector) (connector 'value)) (define (set-value! connector new-value informant) ((connector 'set-value!) new-value informant)) (define (forget-value! connector retractor) ((connector 'forget) retractor)) (define (connect connector new-constraint) ((connector 'connect) new-constraint)) ;;;;;;;;;;;;;;;;; ;; CONSTRAINTS ;; ;;;;;;;;;;;;;;;;; (define (inform-about-value constraint) (constraint 'I-have-a-value)) (define (inform-about-no-value constraint) (constraint 'I-lost-my-value)) (define (adder a1 a2 sum) (define (process-new-value) (cond [(and (has-value? a1) (has-value? a2)) (set-value! sum (+ (get-value a1) (get-value a2)) me)] [(and (has-value? a1) (has-value? sum)) (set-value! a2 (- (get-value sum) (get-value a1)) me)] [(and (has-value? a2) (has-value? sum)) (set-value! a1 (- (get-value sum) (get-value a2)) me)])) (define (process-forget-value) (forget-value! sum me) (forget-value! a1 me) (forget-value! a2 me) (process-new-value)) (define (me request) (cond [(eq? request 'I-have-a-value) (process-new-value)] [(eq? request 'I-lost-my-value) (process-forget-value)] [else (error "Unknown request -- ADDER" request)])) (connect a1 me) (connect a2 me) (connect sum me) me) (define (multiplier m1 m2 product) (define (process-new-value) (cond [(or (and (has-value? m1) (zero? (get-value m1))) (and (has-value? m2) (zero? (get-value m2)))) (set-value! product 0 me)] [(and (has-value? m1) (has-value? m2)) (set-value! product (* (get-value m1) (get-value m2)) me)] [(and (has-value? product) (has-value? m1)) (set-value! m2 (/ (get-value product) (get-value m1)) me)] [(and (has-value? product) (has-value? m2)) (set-value! m1 (/ (get-value product) (get-value m2)) me)])) (define (process-forget-value) (forget-value! product me) (forget-value! m1 me) (forget-value! m2 me) (process-new-value)) (define (me request) (cond [(eq? request 'I-have-a-value) (process-new-value)] [(eq? request 'I-lost-my-value) (process-forget-value)] [else (error "Unknown request -- MULTIPLIER" request)])) (connect m1 me) (connect m2 me) (connect product me) me) (define (constant value connector) (define (me request) (error "Unknown request -- CONSTANT" request)) (connect connector me) (set-value! connector value me) me) (define (probe name connector) (define (print-probe value) (printf "Probe: ~a = ~a ~n" name value)) (define (process-new-value) (print-probe (get-value connector))) (define (process-forget-value) (print-probe "?")) (define (me request) (cond [(eq? request 'I-have-a-value) (process-new-value)] [(eq? request 'I-lost-my-value) (process-forget-value)] [else (error "Unknown request -- PROBE" request)])) (connect connector me) me) ;;;;;;;;;; ;; 3.33 ;; ;;;;;;;;;; (define (averager a b c) (define s (make-connector)) (define t (make-connector)) (adder a b s) (multiplier t c s) (constant 2 t) 'ok) ;; TEST (define A (make-connector)) (define B (make-connector)) (define C (make-connector)) (averager A B C) ;; 'ok (probe "A" A) ;; # (probe "B" B) ;; # (probe "C" C) ;; # (set-value! A 3 'user) ;; Probe: A = 3 ;; 'done (set-value! B 5 'user) ;; Probe: B = 5 ;; Probe: C = 4 ;; 'done (forget-value! B 'user) ;; Probe: B = ? ;; Probe: C = ? ;; 'done (set-value! C 5 'user) ;; Probe: C = 5 ;; Probe: B = 7 ;; 'done ;;;;;;;;;; ;; 3.34 ;; ;;;;;;;;;; (define (bad-squarer a b) (multiplier a a b) 'ok) ;; TEST (define E (make-connector)) (define F (make-connector)) (bad-squarer E F) ;; 'ok (probe "E" E) ;; # (probe "F" F) ;; # (set-value! E 3 'user) ;; Probe: E = 3 ;; Probe: F = 9 ;; 'done (forget-value! E 'user) ;; Probe: E = ? ;; Probe: F = ? ;; 'done (set-value! F 25 'user) ;; Probe: F = 25 ;; 'done ;; Clearing the value for a also clears the value for b. If, after that, you set ;; the value for b, multiplier has no way to propagate the value back to a. ;; Multiplier expects to have two of three values in order to propagate to the ;; third, but here it has only one. So no propagation occurs. ;;;;;;;;;; ;; 3.35 ;; ;;;;;;;;;; (define (squarer a b) (define (process-new-value) (cond [(has-value? b) (if (negative? (get-value b)) (error "Square negative -- SQUARER" (get-value b)) (set-value! a (sqrt (get-value b)) me))] [(has-value? a) (set-value! b (sqr (get-value a)) me)])) (define (process-forget-value) (forget-value! b me) (forget-value! a me) (process-new-value)) (define (me request) (cond [(eq? request 'I-have-a-value) (process-new-value)] [(eq? request 'I-lost-my-value) (process-forget-value)] [else (error "Unknown request -- SQUARER" request)])) (connect a me) (connect b me) me) ;; TEST (define G (make-connector)) (define H (make-connector)) (squarer G H) ;; # (probe "G" G) ;; # (probe "H" H) ;; # (set-value! G 3 'user) ;; Probe: G = 3 ;; Probe: H = 9 ;; 'done (forget-value! G 'user) ;; Probe: G = ? ;; Probe: H = ? ;; 'done (set-value! H 25 'user) ;; Probe: H = 25 ;; Probe: G = 5 ;; 'done ;; Here the constraint does give the square root as expected. ;;;;;;;;;; ;; 3.36 ;; ;;;;;;;;;; ;; Discuss the environments created by: ;; (define a (make-connector)) ;; (set-value! a 10 'user) ;; make-connector is defined in the global environment. So calling it creates a ;; binding frame E1 below the global environment. It has no formal parameters to ;; bind, but it does create local definitions in E1, including set-my-value. ;; set-value! is defined in the global environment. Calling it on a, 10, and ;; 'user creates a binding frame E2, below the global environment, where connector ;; is bound to a (which is the me function in E1), new-value is bound to 10, and ;; informant is bound to 'user. Then the body of set-value! is evaluated in E2. ;; Evaluating the body of set-value! in E2 first calls connector a on 'set-value to ;; get the set-my-value function from E1, and then applies set-my-value to 10 and ;; 'user. ;; Calling set-my-value creates a new binding frame E3 below E1, where newval is ;; bound to 10 and setter is bound to 'user. Then its body is evaluated in E3, ;; and, as part of that, the for-each-except function is called. ;; Calling for-each-except creates a new binding frame E4, below its enclosing ;; environment, which is the global environment. Here in E4, the formal ;; parameters exception, procedure, and lst are bound to the setter, ;; inform-about-value, and constraints passed from E3. Then the body of ;; for-each-except is run in E4, but this means calling inform-about-value on all ;; the constraints involving a. Since the enclosing environment for ;; inform-about-value is E1, all these evaluations take place in binding frames ;; below E1 and thus have access to all a's local state. ;;;;;;;;;; ;; 3.37 ;; ;;;;;;;;;; (define (c+ x y) (define z (make-connector)) (adder x y z) z) (define (c* x y) (define z (make-connector)) (multiplier x y z) z) (define (c/ x y) (define z (make-connector)) (multiplier y z x) z) (define (cv value) (define z (make-connector)) (constant value z) z) (define (celsius-fahrenheit-converter x) (c+ (c* (c/ (cv 9) (cv 5)) x) (cv 32))) ;; TEST (define C1 (make-connector)) (define F1 (celsius-fahrenheit-converter C1)) (probe "C1" C1) ;; # (probe "F1" F1) ;; # (set-value! C1 25 'user) ;; Probe: C1 = 25 ;; Probe: F1 = 77 ;; 'done (forget-value! C1 'user) ;; Probe: C1 = ? ;; Probe: F1 = ? ;; 'done (set-value! F1 212 'user) ;; Probe: F1 = 212 ;; Probe: C1 = 100 ;; 'done