你这样定义它:
(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
表单创建的环境框架内。
现在应用h
toh
创建新的环境框架,其中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
等等。
无论是新的闭包对象还是相同的闭包对象将被重用,取决于编译器。这可能会影响性能,但不会影响递归的语义。