#lang racket
(require racket/mpair
"4-2-2-lazy-repl-program.rkt")
;;;;;;;;;;
;; 4.27 ;;
;;;;;;;;;;
;; The lazy interpreter incremented count once when defining w and once again when
;; evaluating w. However, further evaluations of w did not increment count.
;; When we define w, my-eval is called on the definition-value, which is not delayed.
;; So the set! in the outermost call to id is executed, and count is incremented to
;; one. However, the inner call to id, being the argument to a compound procedure,
;; is delayed, and the second set! is not executed during the definition of w.
;; Evaluating w then forces the delayed inner call to id, and the second set!
;; statement is executed, incrementing count to two. Because of memoization, w will
;; not increment count again.
;; (driver-loop)
;; ;;; L-Eval input:
;; (define count 0)
;; ;;; L-Eval value:
;; ok
;; ;;; L-Eval input:
;; (define (id x) (set! count (+ count 1)) x)
;; ;;; L-Eval value:
;; ok
;; ;;; L-Eval input:
;; count
;; ;;; L-Eval value:
;; 0
;; ;;; L-Eval input:
;; (define w (id (id 10)))
;; ;;; L-Eval value:
;; ok
;; ;;; L-Eval input:
;; count
;; ;;; L-Eval value:
;; 1
;; ;;; L-Eval input:
;; w
;; ;;; L-Eval value:
;; 10
;; ;;; L-Eval input:
;; count
;; ;;; L-Eval value:
;; 2
;; ;;; L-Eval input:
;; w
;; ;;; L-Eval value:
;; 10
;; ;;; L-Eval input:
;; count
;; ;;; L-Eval value:
;; 2
;;;;;;;;;;
;; 4.28 ;;
;;;;;;;;;;
(define (f g) (g 2)) ; f of g is 'apply g to 2'
;; Since g is the argument to a compound procedure, it gets delayed. If we did not
;; force procedure operations, we couldn't apply g to 2 in the body. It would be a
;; thunk containing a procedure but not itself a procedure.
;; WITH MY-EVAL USING ACTUAL-VALUE AS IN THE BOOK:
;; [(application? expr) ; the application clause in my-eval
;; (my-apply (actual-value (operator expr) env)
;; (operands expr)
;; env)]
;; ;;; L-Eval input:
;; (define (f g) (g 2))
;; ;;; L-Eval value:
;; ok
;; ;;; L-Eval input:
;; (define (plus3 x) (+ x 3))
;; ;;; L-Eval value:
;; ok
;; ;;; L-Eval input:
;; (f plus3)
;; ;;; L-Eval value:
;; 5
;; WITH MY-EVAL CHANGED TO NOT FORCE THE OPERATOR:
;; [(application? expr)
;; (my-apply (my-eval (operator expr) env)
;; (operands expr)
;; env)]
;; (driver-loop)
;; ;;; L-Eval input:
;; (define (f g) (g 2))
;; ;;; L-Eval value:
;; ok
;; ;;; L-Eval input:
;; (define (plus3 x) (+ x 3))
;; ;;; L-Eval value:
;; ok
;; ;;; L-Eval input:
;; (f plus3)
;; . . Unknown procedure type -- APPLY {thunk plus3 ...
;;;;;;;;;;
;; 4.29 ;;
;;;;;;;;;;
;; Any recursive function with side-effects would run much more slowly without
;; memoization than with memoization, when many loops were called for.
;; WITH MEMOIZATION:
;; Count is incremented once in the evaluation of (square (id 10)).
;; (driver-loop)
;; ;;; L-Eval input:
;; (define count 0)
;; ;;; L-Eval value:
;; ok
;; ;;; L-Eval input:
;; (define (id x) (set! count (+ count 1)) x)
;; ;;; L-Eval value:
;; ok
;; ;;; L-Eval input:
;; (define (square x) (* x x))
;; ;;; L-Eval value:
;; ok
;; ;;; L-Eval input:
;; (square (id 10))
;; ;;; L-Eval value:
;; 100
;; ;;; L-Eval input:
;; count
;; ;;; L-Eval value:
;; 1
;; WITHOUT MEMOIZATION:
;; To not memoize, use this definition for force-it:
;; (define (force-it obj)
;; (if (thunk? obj)
;; (actual-value (thunk-exp obj) (thunk-env obj))
;; obj))
;; Count is incremented twice in the evaluation of (square (id 10)).
;; (driver-loop)
;; ;;; L-Eval input:
;; (define count 0)
;; ;;; L-Eval value:
;; ok
;; ;;; L-Eval input:
;; (define count 0)
;; ;;; L-Eval value:
;; ok
;; ;;; L-Eval input:
;; (define (id x) (set! count (+ count 1)) x)
;; ;;; L-Eval value:
;; ok
;; ;;; L-Eval input:
;; (define (square x) (* x x))
;; ;;; L-Eval value:
;; ok
;; ;;; L-Eval input:
;; (square (id 10))
;; ;;; L-Eval value:
;; 100
;; ;;; L-Eval input:
;; count
;; ;;; L-Eval value:
;; 2
;; (id 10) is evaluated twice in the body of square. Without memoization, this
;; triggers two executions of the set!. With memoization, set! is executed only
;; once.
;;;;;;;;;;
;; 4.30 ;;
;;;;;;;;;;
;;;;;;;;
;; A. ;;
;;;;;;;;
;; Add these to the list of primitive procedures to make the example work:
;; (list 'displayln displayln)
;; (list 'list list)
;; WITH EVAL-SEQUENCE AS IN THE BOOK:
(my-eval
'(define (for-each proc items)
(if (empty? items)
'done
(begin (proc (car items))
(for-each proc (cdr items)))))
the-global-environment)
;; 'ok
(my-eval
'(for-each (lambda (x) (displayln x)) (list 57 321 88))
the-global-environment)
;; 57
;; 321
;; 88
;; 'done
;; So the book code does work for this example. This is because the argument x is
;; passed to the primitive procedure displayln, and primitive procedures always force
;; their arguments.
;;;;;;;;
;; B. ;;
;;;;;;;;
;; WITH EVAL-SEQUENCE AS IN THE BOOK:
(my-eval
'(define (p1 x)
(set! x (cons x '(2)))
x)
the-global-environment)
;; 'ok
(my-eval
'(define (p2 x)
(define (p e)
e
x)
(p (set! x (cons x '(2)))))
the-global-environment)
;; 'ok
(my-eval '(p1 1) the-global-environment)
;; '(1 2)
(my-eval '(p2 1) the-global-environment)
;; (mcons
;; 'thunk
;; (mcons
;; 1
;; (mcons ... ; cut out a huge environment list here
;; WITH CY D. FECT'S VERSION OF EVAL-SEQUENCE:
;; (my-eval
;; '(define (p1 x)
;; (set! x (cons x '(2)))
;; x)
;; the-global-environment)
;; ;; 'ok
;; (my-eval
;; '(define (p2 x)
;; (define (p e)
;; e
;; x)
;; (p (set! x (cons x '(2)))))
;; the-global-environment)
;; ;; 'ok
;; (my-eval '(p1 1) the-global-environment)
;; ;; '(1 2)
;; (my-eval '(p2 1) the-global-environment)
;; ;; '(1 2)
;; So Cy's version 'does the right thing' and forces e in the definition of p2, while
;; the book code does not.
;;;;;;;;
;; C. ;;
;;;;;;;;
;; The arguments to the lambda in part a. were already forced by the call to the
;; primitive procedure display. So pre-forcing them, as Cy wants, would not affect
;; the behavior of the for-each.
;;;;;;;;
;; D. ;;
;;;;;;;;
;; I can see the utility of Cy's approach. It does the right thing with
;; side-effects, but programs using lazy evaluation should not have side-effects
;; anyway. I like the book's consistent approach: if we're going to have a lazy
;; evaluator, then it should be consistently lazy, and not just lazy except in
;; special circumstances.
;;;;;;;;;;
;; 4.31 ;;
;;;;;;;;;;
;; The idea is to make all lambda parameters "typed". So now a lambda parameter will
;; be a two-element list (<var> <type>) where the type is either 'eager, 'lazy, or
;; 'lazy-memo. However, the user does not have to specify the 'eager type when
;; defining a procedure. Parameters are 'eager by default.
;; The make-all-typed procedure will add the 'eager type to regular arguments when
;; constructing a lambda. So make-all-typed will have to be used everywhere that
;; make-lambda is used in the program.
;; Once all procedure parameters are typed, we need a new procedure selector,
;; procedure-types.
;; Lastly, we change my-apply to use a new function list-of-args, to replace
;; list-of-delayed-args. list-of-args will take an extra parameter, the types of the
;; arguments, to construct the appropriate list of values and/or thunks.
;; Change define:
(define (definition? expr)
(tagged-list? expr 'define))
(define (procedure-definition? expr)
(and (definition? expr)
(not (symbol? (second expr)))))
(define (definition-variable expr)
(if (procedure-definition? expr)
(first (second expr))
(second expr)))
(define (definition-parameters expr)
(rest (second expr)))
(define (make-all-typed params)
(map (lambda (p)(if (symbol? p)
(list p 'eager)
p))
params))
(define (definition-value expr)
(if (procedure-definition? expr)
(make-lambda (make-all-typed (definition-parameters expr)) ; changed
(drop expr 2))
(third expr)))
;; Change make-lambda to type the parameters:
(define (make-lambda parameters body)
(cons 'lambda (cons (make-all-typed parameters) body))) ; changed
;; Change thunks, force-it, and delay-it:
(define (thunk-memo? obj) (tagged-list? obj 'thunk-memo))
(define (thunk-no-memo? obj) (tagged-list? obj 'thunk-no-memo))
(define (thunk-exp my-thunk) (mcar (mcdr my-thunk)))
(define (thunk-env my-thunk) (mcar (mcdr (mcdr my-thunk))))
(define (delay-it-memo expr env)
(mlist 'thunk-memo expr env))
(define (delay-it-no-memo expr env)
(mlist 'thunk-no-memo expr env))
(define (evaluated-thunk? obj) (tagged-list? obj 'evaluated-thunk))
(define (thunk-value evaluated-thunk) (mcar (mcdr evaluated-thunk)))
(define (force-it obj)
(cond [(thunk-memo? obj)
(define result (actual-value (thunk-exp obj) (thunk-env obj)))
(set-mcar! obj 'evaluated-thunk)
(set-mcar! (mcdr obj) result)
(set-mcdr! (mcdr obj) empty)
result]
[(evaluated-thunk? obj) (thunk-value obj)]
[(thunk-no-memo? obj)
(actual-value (thunk-exp obj) (thunk-env obj))]
[else obj]))
;; Change procedure selectors:
(define (procedure-parameters p) (map first (second p)))
(define (procedure-types p) (map second (second p)))
;; Now we will change list-of-delayed-args to a new function list-of-args. The new
;; function list-of-args will take three parameters, arguments, types, and env, and
;; produce the appropriate list of values and/or thunks according to the types of the
;; procedure arguments.
(define (list-of-args exprs types env)
(cond [(no-operands? exprs)
empty]
[else
(define e (first-operand exprs))
(define t (first types))
(define arg (cond [(eq? t 'eager) (actual-value e env)]
[(eq? t 'lazy) (delay-it-no-memo e env)]
[(eq? t 'lazy-memo) (delay-it-memo e env)]
[else (error "Unknown type -- LIST-OF-ARGS" t)]))
(cons arg (list-of-args (rest-operands exprs)
(rest types)
env))]))
;; Lastly change my-apply to use list-of-args:
(define (my-apply procedure arguments env)
(cond [(primitive-procedure? procedure)
(apply-primitive-procedure
procedure
(list-of-arg-values arguments env))]
[(compound-procedure? procedure)
(eval-sequence
(procedure-body procedure)
(extend-environment
(procedure-parameters procedure)
(list-of-args arguments
(procedure-types procedure)
env)
(procedure-environment procedure)))]
[else (error "Unknown procedure type -- APPLY" procedure)]))
;; See "4-2-2-optionally-lazy-repl-program.rkt" for the program with some tests at
;; the end of the file.
Comments
0 B
|👍
/👎
0 B
|👍
/👎