你可以像这样定义它:
你可以像这样定义它:
(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 应用于 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
- 作为一个
闭包对象。也就是说,它是一个带有一个参数
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
等。
是否将使用新的闭包对象或重用同一闭包对象取决于编译器。 这可能会对性能产生影响,但不会影响递归的语义。
lambda
,写成((f f) x)
而不是(f f x)
。 - Will Ness