方案中按需调用

问题描述

我有这段代码知道参数是使用调用传递的:

(define fact-2
  (let ((foo (lambda (n f)
               (if (zero? n)
                   1
                   (f n f)))))
    (lambda (n)
      (let ((res 1))
        (foo n (begin 
                 (set! res (* res n)) 
                 (set! n (- n 1)) 
                 foo)) 
        res))))

我觉得我错过了一些东西,但是在call by need调用 foo 并将此对象作为 f,它会计算一次 f 并且then 永远不会更新 resn。这样对吗?我错过了什么吗?

谢谢。

解决方法

你说得对。只有使用 call-by-name 时,begin 表达式才会在每次调用 f 时得到评估(第一次除外)——以找出它是什么我们正在调用,仅当确实调用了 f

使用call-by-valuebegin 表达式将只计算一次,在第一次调用 foo 之前。

call-by-need可能最多被评估一次,如果在第一次调用 foo 结束时有一个需要再次调用 f


让我们看看如何在常规的 call-by-value 方案中模拟 call-by-name 版本:

(define fact2
  (let ((foo (lambda (n f)
               (if (zero? (n))       ;; (n)   NB
                   1
                   ((f) n f)))))     ;; (f)   NB
    (lambda (n)
      (let ((res 1))
        (foo (lambda () n)           ;; (lambda () ...)    NB
             (lambda ()              ;; (lambda () ...)    NB
               (begin 
                 (set! res (* res n)) 
                 (set! n (- n 1)) 
                 foo))) 
        res))))

在 Racket 中调用 (fact2 5) 会产生 120

对于 call-by-value 语义,您的代码不需要更改(在常规的 call-by-value 方案中解释)并且当然会循环,对于(fact-2 5) 调用(在 Racket 中确实如此)。

并且在 call-by-need 语义下,每个 lambda 的主体(两个新的 lambda 包装器的)将仅在第一次被调用时进行评估,随后它将保存计算出的值然后归还;并且对于所有后续调用,将立即返回保存的值,而不评估主体。因此,set! 表单最多会被评估一次,并且带有示例测试调用 (fact-2 5) 的代码将再次循环。