在像 Lazy Racket 这样的惰性语言中,您可以使用普通顺序版本,但不能使用像 Scheme 这样的任何应用顺序编程语言。他们只会进入无限循环。
Y 的应用版本通常称为 Z 组合子:
(define Z
(lambda (f)
((lambda (g) (g g))
(lambda (g)
(f (lambda args (apply (g g) args)))))))
现在应用 this 时发生的第一件事是(g g)
,因为您总是可以用扩展它的主体来替换整个应用程序,所以函数的主体可以重写为:
(define Z
(lambda (f)
((lambda (g)
(f (lambda args (apply (g g) args))))
(lambda (g)
(f (lambda args (apply (g g) args)))))))
我并没有真正改变任何东西。只需多一点代码就可以实现完全相同的功能。请注意,此版本用于apply
支持多个参数函数。想象一下阿克曼函数:
(define ackermann
(lambda (m n)
(cond
((= m 0) (+ n 1))
((= n 0) (ackermann (- m 1) 1))
(else (ackermann (- m 1) (ackermann m (- n 1)))))))
(ackermann 3 6) ; ==> 509
这可以这样做Z
:
((Z (lambda (ackermann)
(lambda (m n)
(cond
((= m 0) (+ n 1))
((= n 0) (ackermann (- m 1) 1))
(else (ackermann (- m 1) (ackermann m (- n 1))))))))
3
6) ; ==> 509
请注意,实现完全相同,不同之处在于如何处理对自身的引用。
编辑
所以你在问评估是如何延迟的。正常的订单版本如下所示:
(define Y
(lambda (f)
((lambda (g) (g g))
(lambda (g) (f (g g))))))
如果您查看如何将其与参数一起应用,您会注意到 Y 永远不会返回,因为在它可以应用之前f
它(f (g g))
需要评估(g g)
哪个反过来评估(f (g g))
等等。为了挽救我们不(g g)
立即应用的情况。我们知道(g g)
成为一个函数,所以我们只给出f
一个函数,当应用它时会生成实际的函数并应用它。如果你有一个函数add1
,你可以制作一个你可以使用的包装器(lambda (x) (add1 x))
,它会起作用。以相同的方式(lambda args (apply (g g) args))
是相同的(g g)
,您可以通过应用替换规则来看到这一点。这里的线索是,这有效地停止了每一步的计算,直到它真正投入使用。