Advertisement
timothy235

sicp-4-2-2-an-interpreter-with-lazy-evaluation

Mar 19th, 2017
148
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Racket 11.18 KB | None | 0 0
  1. #lang racket
  2. (require racket/mpair
  3.          "4-2-2-lazy-repl-program.rkt")
  4.  
  5. ;;;;;;;;;;
  6. ;; 4.27 ;;
  7. ;;;;;;;;;;
  8.  
  9. ;; The lazy interpreter incremented count once when defining w and once again when
  10. ;; evaluating w.  However, further evaluations of w did not increment count.
  11.  
  12. ;; When we define w, my-eval is called on the definition-value, which is not delayed.
  13. ;; So the set! in the outermost call to id is executed, and count is incremented to
  14. ;; one.  However, the inner call to id, being the argument to a compound procedure,
  15. ;; is delayed, and the second set! is not executed during the definition of w.
  16.  
  17. ;; Evaluating w then forces the delayed inner call to id, and the second set!
  18. ;; statement is executed, incrementing count to two.  Because of memoization, w will
  19. ;; not increment count again.
  20.  
  21. ;; (driver-loop)
  22. ;; ;;; L-Eval input:
  23. ;; (define count 0)
  24. ;; ;;; L-Eval value:
  25. ;; ok
  26. ;; ;;; L-Eval input:
  27. ;; (define (id x) (set! count (+ count 1)) x)
  28. ;; ;;; L-Eval value:
  29. ;; ok
  30. ;; ;;; L-Eval input:
  31. ;; count
  32. ;; ;;; L-Eval value:
  33. ;; 0
  34. ;; ;;; L-Eval input:
  35. ;; (define w (id (id 10)))
  36. ;; ;;; L-Eval value:
  37. ;; ok
  38. ;; ;;; L-Eval input:
  39. ;; count
  40. ;; ;;; L-Eval value:
  41. ;; 1
  42. ;; ;;; L-Eval input:
  43. ;; w
  44. ;; ;;; L-Eval value:
  45. ;; 10
  46. ;; ;;; L-Eval input:
  47. ;; count
  48. ;; ;;; L-Eval value:
  49. ;; 2
  50. ;; ;;; L-Eval input:
  51. ;; w
  52. ;; ;;; L-Eval value:
  53. ;; 10
  54. ;; ;;; L-Eval input:
  55. ;; count
  56. ;; ;;; L-Eval value:
  57. ;; 2
  58.  
  59. ;;;;;;;;;;
  60. ;; 4.28 ;;
  61. ;;;;;;;;;;
  62.  
  63. (define (f g) (g 2)) ; f of g is 'apply g to 2'
  64.  
  65. ;; Since g is the argument to a compound procedure, it gets delayed.  If we did not
  66. ;; force procedure operations, we couldn't apply g to 2 in the body.  It would be a
  67. ;; thunk containing a procedure but not itself a procedure.
  68.  
  69. ;; WITH MY-EVAL USING ACTUAL-VALUE AS IN THE BOOK:
  70.  
  71.         ;; [(application? expr) ; the application clause in my-eval
  72.          ;; (my-apply (actual-value (operator expr) env)
  73.                    ;; (operands expr)
  74.                    ;; env)]
  75.  
  76. ;; ;;; L-Eval input:
  77. ;; (define (f g) (g 2))
  78. ;; ;;; L-Eval value:
  79. ;; ok
  80. ;; ;;; L-Eval input:
  81. ;; (define (plus3 x) (+ x 3))
  82. ;; ;;; L-Eval value:
  83. ;; ok
  84. ;; ;;; L-Eval input:
  85. ;; (f plus3)
  86. ;; ;;; L-Eval value:
  87. ;; 5
  88.  
  89. ;; WITH MY-EVAL CHANGED TO NOT FORCE THE OPERATOR:
  90.  
  91.         ;; [(application? expr)
  92.          ;; (my-apply (my-eval (operator expr) env)
  93.                    ;; (operands expr)
  94.                    ;; env)]
  95.  
  96. ;; (driver-loop)
  97. ;; ;;; L-Eval input:
  98. ;; (define (f g) (g 2))
  99. ;; ;;; L-Eval value:
  100. ;; ok
  101. ;; ;;; L-Eval input:
  102. ;; (define (plus3 x) (+ x 3))
  103. ;; ;;; L-Eval value:
  104. ;; ok
  105. ;; ;;; L-Eval input:
  106. ;; (f plus3)
  107. ;; . . Unknown procedure type -- APPLY {thunk plus3 ...
  108.  
  109. ;;;;;;;;;;
  110. ;; 4.29 ;;
  111. ;;;;;;;;;;
  112.  
  113. ;; Any recursive function with side-effects would run much more slowly without
  114. ;; memoization than with memoization, when many loops were called for.
  115.  
  116. ;; WITH MEMOIZATION:
  117.  
  118. ;; Count is incremented once in the evaluation of (square (id 10)).
  119.  
  120. ;; (driver-loop)
  121. ;; ;;; L-Eval input:
  122. ;; (define count 0)
  123. ;; ;;; L-Eval value:
  124. ;; ok
  125. ;; ;;; L-Eval input:
  126. ;; (define (id x) (set! count (+ count 1)) x)
  127. ;; ;;; L-Eval value:
  128. ;; ok
  129. ;; ;;; L-Eval input:
  130. ;; (define (square x) (* x x))
  131. ;; ;;; L-Eval value:
  132. ;; ok
  133. ;; ;;; L-Eval input:
  134. ;; (square (id 10))
  135. ;; ;;; L-Eval value:
  136. ;; 100
  137. ;; ;;; L-Eval input:
  138. ;; count
  139. ;; ;;; L-Eval value:
  140. ;; 1
  141.  
  142. ;; WITHOUT MEMOIZATION:
  143.  
  144. ;; To not memoize, use this definition for force-it:
  145.  
  146. ;; (define (force-it obj)
  147.   ;; (if (thunk? obj)
  148.     ;; (actual-value (thunk-exp obj) (thunk-env obj))
  149.     ;; obj))
  150.  
  151. ;; Count is incremented twice in the evaluation of (square (id 10)).
  152.  
  153. ;; (driver-loop)
  154. ;; ;;; L-Eval input:
  155. ;; (define count 0)
  156. ;; ;;; L-Eval value:
  157. ;; ok
  158. ;; ;;; L-Eval input:
  159. ;; (define count 0)
  160. ;; ;;; L-Eval value:
  161. ;; ok
  162. ;; ;;; L-Eval input:
  163. ;; (define (id x) (set! count (+ count 1)) x)
  164. ;; ;;; L-Eval value:
  165. ;; ok
  166. ;; ;;; L-Eval input:
  167. ;; (define (square x) (* x x))
  168. ;; ;;; L-Eval value:
  169. ;; ok
  170. ;; ;;; L-Eval input:
  171. ;; (square (id 10))
  172. ;; ;;; L-Eval value:
  173. ;; 100
  174. ;; ;;; L-Eval input:
  175. ;; count
  176. ;; ;;; L-Eval value:
  177. ;; 2
  178.  
  179. ;; (id 10) is evaluated twice in the body of square.  Without memoization, this
  180. ;; triggers two executions of the set!.  With memoization, set! is executed only
  181. ;; once.
  182.  
  183. ;;;;;;;;;;
  184. ;; 4.30 ;;
  185. ;;;;;;;;;;
  186.  
  187. ;;;;;;;;
  188. ;; A. ;;
  189. ;;;;;;;;
  190.  
  191. ;; Add these to the list of primitive procedures to make the example work:
  192.    
  193.     ;; (list 'displayln displayln)
  194.     ;; (list 'list list)
  195.  
  196. ;; WITH EVAL-SEQUENCE AS IN THE BOOK:
  197.  
  198. (my-eval
  199.   '(define (for-each proc items)
  200.      (if (empty? items)
  201.        'done
  202.        (begin (proc (car items))
  203.               (for-each proc (cdr items)))))
  204.   the-global-environment)
  205. ;; 'ok
  206. (my-eval
  207.   '(for-each (lambda (x) (displayln x)) (list 57 321 88))
  208.   the-global-environment)
  209. ;; 57
  210. ;; 321
  211. ;; 88
  212. ;; 'done
  213.  
  214. ;; So the book code does work for this example.  This is because the argument x is
  215. ;; passed to the primitive procedure displayln, and primitive procedures always force
  216. ;; their arguments.
  217.  
  218. ;;;;;;;;
  219. ;; B. ;;
  220. ;;;;;;;;
  221.  
  222. ;; WITH EVAL-SEQUENCE AS IN THE BOOK:
  223.  
  224. (my-eval
  225.   '(define (p1 x)
  226.      (set! x (cons x '(2)))
  227.      x)
  228.   the-global-environment)
  229. ;; 'ok
  230. (my-eval
  231.   '(define (p2 x)
  232.      (define (p e)
  233.        e
  234.        x)
  235.      (p (set! x (cons x '(2)))))
  236.   the-global-environment)
  237. ;; 'ok
  238. (my-eval '(p1 1) the-global-environment)
  239. ;; '(1 2)
  240. (my-eval '(p2 1) the-global-environment)
  241. ;; (mcons
  242.  ;; 'thunk
  243.  ;; (mcons
  244.   ;; 1
  245.   ;; (mcons ... ; cut out a huge environment list here
  246.  
  247. ;; WITH CY D. FECT'S VERSION OF EVAL-SEQUENCE:
  248.  
  249. ;; (my-eval
  250.   ;; '(define (p1 x)
  251.      ;; (set! x (cons x '(2)))
  252.      ;; x)
  253.   ;; the-global-environment)
  254. ;; ;; 'ok
  255. ;; (my-eval
  256.   ;; '(define (p2 x)
  257.      ;; (define (p e)
  258.        ;; e
  259.        ;; x)
  260.      ;; (p (set! x (cons x '(2)))))
  261.   ;; the-global-environment)
  262. ;; ;; 'ok
  263. ;; (my-eval '(p1 1) the-global-environment)
  264. ;; ;; '(1 2)
  265. ;; (my-eval '(p2 1) the-global-environment)
  266. ;; ;; '(1 2)
  267.  
  268. ;; So Cy's version 'does the right thing' and forces e in the definition of p2, while
  269. ;; the book code does not.
  270.  
  271. ;;;;;;;;
  272. ;; C. ;;
  273. ;;;;;;;;
  274.  
  275. ;; The arguments to the lambda in part a. were already forced by the call to the
  276. ;; primitive procedure display.  So pre-forcing them, as Cy wants, would not affect
  277. ;; the behavior of the for-each.
  278.  
  279. ;;;;;;;;
  280. ;; D. ;;
  281. ;;;;;;;;
  282.  
  283. ;; I can see the utility of Cy's approach.  It does the right thing with
  284. ;; side-effects, but programs using lazy evaluation should not have side-effects
  285. ;; anyway.  I like the book's consistent approach:  if we're going to have a lazy
  286. ;; evaluator, then it should be consistently lazy, and not just lazy except in
  287. ;; special circumstances.
  288.  
  289. ;;;;;;;;;;
  290. ;; 4.31 ;;
  291. ;;;;;;;;;;
  292.  
  293. ;; The idea is to make all lambda parameters "typed".  So now a lambda parameter will
  294. ;; be a two-element list (<var> <type>) where the type is either 'eager, 'lazy, or
  295. ;; 'lazy-memo.  However, the user does not have to specify the 'eager type when
  296. ;; defining a procedure.  Parameters are 'eager by default.
  297.  
  298. ;; The make-all-typed procedure will add the 'eager type to regular arguments when
  299. ;; constructing a lambda.  So make-all-typed will have to be used everywhere that
  300. ;; make-lambda is used in the program.
  301.  
  302. ;; Once all procedure parameters are typed, we need a new procedure selector,
  303. ;; procedure-types.
  304.  
  305. ;; Lastly, we change my-apply to use a new function list-of-args, to replace
  306. ;; list-of-delayed-args.  list-of-args will take an extra parameter, the types of the
  307. ;; arguments, to construct the appropriate list of values and/or thunks.
  308.  
  309. ;; Change define:
  310.  
  311. (define (definition? expr)
  312.   (tagged-list? expr 'define))
  313. (define (procedure-definition? expr)
  314.   (and (definition? expr)
  315.        (not (symbol? (second expr)))))
  316. (define (definition-variable expr)
  317.   (if (procedure-definition? expr)
  318.     (first (second expr))
  319.     (second expr)))
  320. (define (definition-parameters expr)
  321.   (rest (second expr)))
  322.  
  323. (define (make-all-typed params)
  324.   (map (lambda (p)(if (symbol? p)
  325.                      (list p 'eager)
  326.                      p))
  327.        params))
  328.  
  329. (define (definition-value expr)
  330.   (if (procedure-definition? expr)
  331.     (make-lambda (make-all-typed (definition-parameters expr)) ; changed
  332.                  (drop expr 2))
  333.     (third expr)))
  334.  
  335. ;; Change make-lambda to type the parameters:
  336.  
  337. (define (make-lambda parameters body)
  338.   (cons 'lambda (cons (make-all-typed parameters) body))) ; changed
  339.  
  340. ;; Change thunks, force-it, and delay-it:
  341.  
  342. (define (thunk-memo? obj) (tagged-list? obj 'thunk-memo))
  343. (define (thunk-no-memo? obj) (tagged-list? obj 'thunk-no-memo))
  344. (define (thunk-exp my-thunk) (mcar (mcdr my-thunk)))
  345. (define (thunk-env my-thunk) (mcar (mcdr (mcdr my-thunk))))
  346.  
  347. (define (delay-it-memo expr env)
  348.   (mlist 'thunk-memo expr env))
  349. (define (delay-it-no-memo expr env)
  350.   (mlist 'thunk-no-memo expr env))
  351.  
  352. (define (evaluated-thunk? obj) (tagged-list? obj 'evaluated-thunk))
  353. (define (thunk-value evaluated-thunk) (mcar (mcdr evaluated-thunk)))
  354.  
  355. (define (force-it obj)
  356.   (cond [(thunk-memo? obj)
  357.          (define result (actual-value (thunk-exp obj) (thunk-env obj)))
  358.          (set-mcar! obj 'evaluated-thunk)
  359.          (set-mcar! (mcdr obj) result)
  360.          (set-mcdr! (mcdr obj) empty)
  361.          result]
  362.         [(evaluated-thunk? obj) (thunk-value obj)]
  363.         [(thunk-no-memo? obj)
  364.          (actual-value (thunk-exp obj) (thunk-env obj))]
  365.         [else obj]))
  366.  
  367. ;; Change procedure selectors:
  368.  
  369. (define (procedure-parameters p) (map first (second p)))
  370. (define (procedure-types p) (map second (second p)))
  371.  
  372. ;; Now we will change list-of-delayed-args to a new function list-of-args.  The new
  373. ;; function list-of-args will take three parameters, arguments, types, and env, and
  374. ;; produce the appropriate list of values and/or thunks according to the types of the
  375. ;; procedure arguments.
  376.  
  377. (define (list-of-args exprs types env)
  378.   (cond [(no-operands? exprs)
  379.         empty]
  380.         [else
  381.           (define e (first-operand exprs))
  382.           (define t (first types))
  383.           (define arg (cond [(eq? t 'eager) (actual-value e env)]
  384.                             [(eq? t 'lazy) (delay-it-no-memo e env)]
  385.                             [(eq? t 'lazy-memo) (delay-it-memo e env)]
  386.                             [else (error "Unknown type -- LIST-OF-ARGS" t)]))
  387.           (cons arg (list-of-args (rest-operands exprs)
  388.                                   (rest types)
  389.                                   env))]))
  390.  
  391. ;; Lastly change my-apply to use list-of-args:
  392.  
  393. (define (my-apply procedure arguments env)
  394.   (cond [(primitive-procedure? procedure)
  395.          (apply-primitive-procedure
  396.            procedure
  397.            (list-of-arg-values arguments env))]
  398.         [(compound-procedure? procedure)
  399.          (eval-sequence
  400.            (procedure-body procedure)
  401.            (extend-environment
  402.              (procedure-parameters procedure)
  403.              (list-of-args arguments
  404.                            (procedure-types procedure)
  405.                            env)
  406.              (procedure-environment procedure)))]
  407.         [else (error "Unknown procedure type -- APPLY" procedure)]))
  408.  
  409. ;; See "4-2-2-optionally-lazy-repl-program.rkt" for the program with some tests at
  410. ;; the end of the file.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement