Add exercise 4.30

This commit is contained in:
Oliver Payne 2023-08-29 22:31:35 +01:00
parent 6abfa8e373
commit 9d454b2a26
1 changed files with 95 additions and 0 deletions

95
mceval/4-30.rkt Normal file
View File

@ -0,0 +1,95 @@
;; for-each is defined:
;; (define (for-each (proc lazy) (items lazy))
;; (if (null? items)
;; 'done
;; (begin (proc (car items))
;; (for-each proc (cdr items)))))
;; Part a: using the eval-sequence from leval handles sequenced side
;; effects in for-each correctly. The example evaluates as follows:
;; (for-each (lambda (x) (newline) (display x))
;; (list 57 321 88))
;; (begin ((lambda (x) (newline) (display x)) 57)
;; (begin ((lambda (x) (newline) (display x)) 321)
;; (begin ((lambda (x) (newline) (display x)) 88))))
;; The first begin evaluates the first argument, which is an
;; application, so it forces the procedure and delays its argument.
;; Thus we have:
;; ((lambda (x) (newline) (display x)) '(thunk 57))
;; newline and display are both native procedures, so their arguments
;; are forced, and 57 is displayed. The rest of the values follow
;; similarly. The reason the normal-eval sequence works is that the
;; procedure being applied for its side effect calls a primitive
;; procedure.
;; Part b:
;; Define p1 and p2
;; (define (p1 (x lazy))
;; (set! x (cons x '(2)))
;; x)
;; (define (p2 (x lazy))
;; (define (p (e lazy))
;; e
;; x)
;; (p (set! x (cons x '(2)))))
;; Using the original evaluator:
;; (p1 1)
;; -> (set! x (cons 1 '(2))) x
;; ->(1 2)
;; because cons is primitive, so x is forced
;; (p2 1)
;; -> (p '(thunk set! x (cons x '(2))) 1)
;; -> 1
;; because nothing ever uses the argument to p, it is never forced.
;; An alternative eval-sequence that forces all expressions other than
;; the last one to ensure all side effects are carried out.
;; (define (eval-sequence* exps env)
;; (cond ((last-exp? exps) (eval (first-exp exps) env))
;; (else (actual-value (first-exp exps) env)
;; (eval-sequence* (rest-exps exps) env))))
;; Using this:
;; (p1 1)
;; -> (1 2)
;; as before
;; (p2 1)
;; -> (p '(thunk set! x (cons x '(2))))
;; -> (actual-value '(thunk set! x (cons x '(2)))) (actual-value x)
;; -> (1 2)
;; Part c:
;; Using eval-sequence*
;; (for-each (lambda (x) (newline) (display x))
;; (list 57 321 88))
;; -> (begin ((lambda (x) (newline) (display x)) 57)
;; (begin ((lambda (x) (newline) (display x)) 321)
;; (begin ((lambda (x) (newline) (display x)) 88))))
;; This evaluates as for eval-sequence because the procedure only has
;; native procedures which are forced anyway, so there is no
;; difference between the two options.
;; Part d: I prefer eval-sequence*, Cy's approach, as sequences are
;; used for side effects (since only the value of the final
;; expression is returned). So ensuring that side-effects are handled
;; correctly seems like a better option.