你这样定义它:
(let ((fact #f))
(set! fact
(lambda (n) (if (< n 2) 1
(* n (fact (- n 1))))))
(fact 5))
这就是letrec 的真正运作方式。请参阅 Christian Queinnec 的LiSP。
在您询问的示例中,自应用组合器称为"U combinator",
(let ((U (lambda (x) (x x)))
(h (lambda (g)
(lambda (n)
(if (zero? n)
1
(* n ((g g) (sub1 n))))))))
((U h) 5))
这里的微妙之处在于,由于let 的作用域规则,lambda 表达式不能引用正在定义的名称。
当((U h) 5) 被调用时,它被缩减为((h h) 5) 应用程序,在let 表单创建的环境框架内。
现在h 到h 的应用创建了一个新的环境框架,其中g 在它上面的环境中指向h:
(let ((U (lambda (x) (x x)))
(h (lambda (g)
(lambda (n)
(if (zero? n)
1
(* n ((g g) (sub1 n))))))))
( (let ((g h))
(lambda (n)
(if (zero? n)
1
(* n ((g g) (sub1 n))))))
5))
这里的(lambda (n) ...) 表达式是从环境框架内部返回的,其中g 指向它上面的h - 作为closure object。 IE。一个参数n 的函数,它还记住g、h 和U 的绑定。
所以当这个闭包被调用时,n 被赋值为5,并输入了if 形式:
(let ((U (lambda (x) (x x)))
(h (lambda (g)
(lambda (n)
(if (zero? n)
1
(* n ((g g) (sub1 n))))))))
(let ((g h))
(let ((n 5))
(if (zero? n)
1
(* n ((g g) (sub1 n)))))))
(g g) 应用程序被简化为(h h) 应用程序,因为g 指向在创建闭包对象的环境上方的环境框架中定义的h。也就是说,在顶部的let 表单中。但是我们已经看到(h h) 调用的减少,它创建了闭包,即一个参数n 的函数,作为我们的factorial 函数,在下一次迭代中将使用4 调用,然后3等
它是一个新的闭包对象还是相同的闭包对象将被重用,取决于编译器。这可能会影响性能,但不会影响递归的语义。