Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #lang racket
- (require "3-5-streams.rkt"
- "4-1-4-repl-program.rkt")
- ;;;;;;;;;;
- ;; 4.16 ;;
- ;;;;;;;;;;
- (define (lookup-variable-value var env)
- (define b (find-binding-in-env var env))
- (cond [(not b) (error "Unbound variable" var)]
- [else
- (define val (binding-value b))
- (if (eq? val '*unassigned*)
- (error "Unassigned variable" var)
- val)]))
- (define (scan-out-defines body)
- (define defines (filter definition? body))
- (cond [(empty? defines) body]
- [else
- (define rest-of-body (filter-not definition? body))
- (define vars (map definition-variable defines))
- (define vals (map definition-value defines))
- (define bindings
- (for/list ([v vars])
- (list v '*unassigned*)))
- (define assigns
- (for/list ([v vars]
- [e vals])
- (list 'set! v e)))
- (make-let
- bindings
- (append assigns rest-of-body))]))
- ;; test
- (scan-out-defines '((define a 1)
- (define b 2)
- (define (f x) (+ x 5))
- (set! a 3)
- (f (* a b))))
- ;; '(let ((a *unassigned*) (b *unassigned*) (f *unassigned*))
- ;; (set! a 1)
- ;; (set! b 2)
- ;; (set! f (lambda (x) (+ x 5)))
- ;; (set! a 3)
- ;; (f (* a b)))
- ;; The test file ran fine with scan-out-defines installed in either make-procedure or
- ;; procedure-body. I would prefer to put it in procedure-body though because
- ;; scan-out-defines is a syntactic transformation used to aid evaluation, and not a
- ;; syntactic form we are trying to enforce on all procedures. So I think it belongs
- ;; with the selectors rather than the constructors.
- ;;;;;;;;;;
- ;; 4.17 ;;
- ;;;;;;;;;;
- ;; (lambda <vars>
- ;; (define u <e1>)
- ;; (define v <e2>)
- ;; <e3>)
- ;; (lambda <vars>
- ;; (let ((u '*unassigned*)
- ;; (v '*unassigned*))
- ;; (set! u <e1>)
- ;; (set! v <e2>)
- ;; <e3>))
- #|
- Calling the top lambda creates a binding frame below its enclosing environment
- where vars are bound. Then the body is evaluated in that binding frame, which
- adds u and v to the frame, binding them to e1 and e2, respectively, then
- evaluating e3.
- Calling the bottom lambda creates a binding frame where vars are bound, just like
- above. Then the let statement is evaluated in that binding frame. The let is
- treated as a procedure application, so its evaluation creates a new binding frame,
- below the one just created, where u and v are bound to '*unassigned*. Then the
- body of the let statement is evaluated in this subframe, setting u and v to e1 and
- e2, respectively, and then evaluating e3.
- The effect is the same as if vars, u and v, were all defined in the same frame.
- We have, in effect, taken a frame that is a leaf node in the environment graph and
- split it in two, while requiring the body to be evaluated in the new lower frame.
- The body will have access to the same set of bindings as before. The only
- difference is that lookup-variable-value may have to recurse one extra time to
- find the value of a variable left in the upper frame.
- One way to get rid of the extra frame is to have the interpreter scan lambdas for
- internal defines, add any definition-variables to the lambda parameters, curried
- to take the value '*unassigned*, and add the set! statements to the beginning of
- the body.
- |#
- ;;;;;;;;;;
- ;; 4.18 ;;
- ;;;;;;;;;;
- (define (integral delayed-integrand initial-value dt)
- (cons-stream initial-value
- (let ([integrand (force delayed-integrand)])
- (if (stream-null? integrand)
- the-empty-stream
- (integral (delay (stream-cdr integrand))
- (+ (* dt (stream-car integrand))
- initial-value)
- dt)))))
- ;; The define statement involving a let with mutually recursive bindings is rejected
- ;; by the Racket reader:
- ;; (define (solve f y0 dt)
- ;; (let ([y (integral (delay dy) y0 dt)]
- ;; [dy (my-stream-map f y)])
- ;; y))
- ;; ;; . dy: unbound identifier in module in: dy
- ;; In contrast, the '*unassigned* strategy works:
- (define (solve2 f y0 dt)
- (let ([y '*unassigned*]
- [dy '*unassigned*])
- (set! y (integral (delay dy) y0 dt))
- (set! dy (my-stream-map f y))
- y))
- (my-stream-ref (solve2 (lambda (y) y) 1 0.001) 1000)
- ;; 2.716923932235896
- ;; The alternative strategy does not work. The Racket reader accepts the layered
- ;; lets that hide the mutual recursion, but evaluating the inner let expressions
- ;; produces errors because y and dy are still bound to '*unassigned* when passed to
- ;; integral and my-stream-map.
- (define (solve3 f y0 dt)
- (let ([y '*unassigned*]
- [dy '*unassigned*])
- (let ([a (integral (delay dy) y0 dt)]
- [b (my-stream-map f y)])
- (set! y a)
- (set! dy b)
- y)))
- ;; (my-stream-ref (solve3 (lambda (y) y) 1 0.001) 1000)
- ;; ;; . . car: contract violation
- ;; ;; expected: pair?
- ;; ;; given: '*unassigned*
- ;; Scheme also provides letrec, recursive let, that implements 'simultaneous
- ;; defines'. Using internal defines is equivalent to letrec. The Racket style guide
- ;; prefers internal defines over any of the let forms.
- (define (solve4 f y0 dt)
- (letrec ([y (integral (delay dy) y0 dt)]
- [dy (my-stream-map f y)])
- y))
- (my-stream-ref (solve4 (lambda (y) y) 1 0.001) 1000)
- ;; 2.716923932235896
- ;;;;;;;;;;
- ;; 4.19 ;;
- ;;;;;;;;;;
- ;; Racket follows Alyssa's view and produces an 'a undefined' error.
- ;; (let ([a 1])
- ;; (define (f x)
- ;; (define b (+ a x))
- ;; (define a 5)
- ;; (+ a b))
- ;; (f 10))
- ;; ;; . . a: undefined;
- ;; ;; cannot use before initialization
- ;; However, this only produces an error because of the (define a ...) statement in
- ;; the body of f. Remove that and the a from the environment is used:
- (let ([a 1])
- (define (f x)
- (define b (+ a x))
- ;; (define a 5)
- (+ a b))
- (f 10))
- ;; 12
- ;; So Racket is implementing something like our scan-out-defines behind the scenes.
- ;; I don't see an easy way to implement Eva's preferred behavior. I think
- ;; scan-out-defines would have to create a graph of dependencies for the
- ;; definition-variables, and then place values in their locations in an order
- ;; compatible with their dependencies.
- ;;;;;;;;;;
- ;; 4.20 ;;
- ;;;;;;;;;;
- ;; ;; Add this clause to my-eval:
- ;; [(letrec? expr) (my-eval (letrec->simultaneous-lets expr) env)]
- (define (letrec? expr) (tagged-list? expr 'letrec))
- (define (letrec-bindings expr) (second expr))
- (define (letrec-body expr) (drop expr 2))
- (define (letrec->simultaneous-lets expr)
- (define bindings (letrec-bindings expr))
- (cond [(empty? bindings)
- (letrec-body expr)]
- [else
- (define vars (map first bindings))
- (define vals (map second bindings))
- (define new-bindings
- (for/list ([v vars])
- (list v '*unassigned*)))
- (define assigns
- (for/list ([v vars]
- [e vals])
- (list 'set! v e)))
- (make-let
- new-bindings
- (append assigns (letrec-body expr)))]))
- (letrec->simultaneous-lets
- '(letrec () (+ 1 2)))
- ;; '(+ 1 2)
- (letrec->simultaneous-lets
- '(letrec ((even? (lambda (n) (if (zero? n) true (odd? (sub1 n)))))
- (odd? (lambda (n) (if (zero? n) false (even? (sub1 n))))))
- (even? 12)))
- ;; '(let ((even? *unassigned*) (odd? *unassigned*))
- ;; (set! even? (lambda (n) (if (zero? n) true (odd? (sub1 n)))))
- ;; (set! odd? (lambda (n) (if (zero? n) false (even? (sub1 n)))))
- ;; (even? 12))
- (letrec->simultaneous-lets
- '(letrec ((even? (lambda (n) (if (zero? n) true (odd? (sub1 n)))))
- (odd? (lambda (n) (if (zero? n) false (even? (sub1 n))))))
- 'side-effect
- (even? 12)))
- ;; '(let ((even? *unassigned*) (odd? *unassigned*))
- ;; (set! even? (lambda (n) (if (zero? n) true (odd? (sub1 n)))))
- ;; (set! odd? (lambda (n) (if (zero? n) false (even? (sub1 n)))))
- ;; 'side-effect
- ;; (even? 12))
- (define (f x)
- (letrec ([even? (lambda (n) (if (zero? n) true (odd? (sub1 n))))]
- [odd? (lambda (n) (if (zero? n) false (even? (sub1 n))))])
- 'rest-of-body))
- ;; The environment diagram for the letrec is just what you would expect. The letrec
- ;; is transformed into a let, with even? and odd? initially bound to dummy values,
- ;; and then set! to their real lambda values. set!, which evaluates its argument,
- ;; can do this because each variable, even? and odd?, have assigned locations in
- ;; memory due to having already been bound to the dummy values. So neither is a free
- ;; variable in the definition of the other, and no error is raised. In effect, even?
- ;; and odd? are bound simultaneously in the one binding frame, with each value
- ;; depending on the other.
- ;; Using let instead of letrec is a non-starter. The interpreter creates a binding
- ;; frame for the let statement where it tries to bind even? and odd? to the values
- ;; their definitions reduce to. But whichever definition it tries to evaluate first,
- ;; it will always encounter the other, still undefined, raising an error.
- ;;;;;;;;;;
- ;; 4.21 ;;
- ;;;;;;;;;;
- ((lambda (n)
- ((lambda (fact)
- (fact fact n))
- (lambda (ft k)
- (if (= k 1)
- 1
- (* k (ft ft (sub1 k)))))))
- 10)
- ;; 3628800 ; this is 10!
- ((lambda (n)
- ((lambda (fib)
- (fib fib n))
- (lambda (ft k)
- (if (< k 2)
- k
- (+ (ft ft (sub1 k))
- (ft ft (- k 2)))))))
- 10)
- ;; 55 ; this is fib(10)
- (define (f2 x)
- ((lambda (my-even? my-odd?)
- (my-even? my-even? my-odd? x))
- (lambda (ev? od? n)
- (if (zero? n)
- true
- (od? od? ev? (sub1 n))))
- (lambda (od? ev? n)
- (if (zero? n)
- false
- (ev? ev? od? (sub1 n))))))
- (f2 11)
- ;; #f
- (f2 12)
- ;; #t
- ;; I know this is the y-combinator which is a special function from the lambda
- ;; calculus that computes fixed points, and that when applied to a function of two
- ;; variables can be used to implement recursion, and that when applied to a function
- ;; of three variables can be used to implement mutual recursion. But I don't
- ;; understand how it works.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement