你这样定义它:
(let ((fact #f))
(set! fact
(lambda (n) (if (< n 2) 1
(* n (fact (- n 1))))))
(fact 5))
这就是letrec真正的工作方式。参见Christian Queinnec的 LiSP。
在您询问的示例中,自应用组合器称为“ U组合器”,
(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表单创建的环境框架内。
现在应用htoh创建新的环境框架,其中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它上面的那个环境框架内部返回的——作为一个闭包对象。即一个参数的函数,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)call 的减少,它创建了闭包,即一个参数的函数,n作为我们的factorial函数,在下一次迭代中将被调用4,然后3等等。
无论是新的闭包对象还是相同的闭包对象将被重用,取决于编译器。这可能会影响性能,但不会影响递归的语义。