Y Combinator实施方案

Qan*_*asi 5 scheme lambda-calculus y-combinator

我对计划函数式编程真的很陌生。我最近在 lambda 演算中遇到了 Y-combinator 函数,就像这样Y ? (?y.(?x.y(xx))(?x.y(xx)))。我想在方案中实现它,我搜索了很多,但我没有找到任何与上面给出的结构完全匹配的实现。我发现的其中一些如下:

(define Y
(lambda (X)
  ((lambda (procedure)
     (X (lambda (arg) ((procedure procedure) arg))))
   (lambda (procedure)
     (X (lambda (arg) ((procedure procedure) arg)))))))
Run Code Online (Sandbox Code Playgroud)

(define Y
  (lambda (r)
    ((lambda (f) (f f))
     (lambda (y)
       (r (lambda (x) ((y y) x)))))))
Run Code Online (Sandbox Code Playgroud)

如您所见,它们与此Y ? (?y.(?x.y(xx))(?x.y(xx)))组合器函数的结构不匹配。如何以完全相同的方式在方案中实现它?

Syl*_*ter 5

在像 Lazy Racket 这样的惰性语言中,您可以使用正常的顺序版本,但不能在像Scheme 这样的任何应用顺序编程语言中使用。他们只会进入无限循环。

Y 的应用版本通常称为 Z 组合器:

(define Z
  (lambda (f)
    ((lambda (g) (g g))
     (lambda (g)
       (f (lambda args (apply (g g) args)))))))
Run Code Online (Sandbox Code Playgroud)

现在,应用此方法时发生的第一件事是(g g),由于您始终可以用其主体的扩展来替换整个应用程序,因此函数主体可以重写为:

(define Z
  (lambda (f)
    ((lambda (g)
       (f (lambda args (apply (g g) args))))
     (lambda (g)
       (f (lambda args (apply (g g) args)))))))
Run Code Online (Sandbox Code Playgroud)

我并没有真正改变任何事情。只需要多一点代码就可以实现完全相同的功能。请注意,此版本用于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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

请注意,实现完全相同,区别在于如何处理对其自身的引用。

编辑

所以你问评估是如何延迟的。正常订单版本如下所示:

(define Y
  (lambda (f)
    ((lambda (g) (g g))
     (lambda (g) (f (g g))))))
Run Code Online (Sandbox Code Playgroud)

f如果你看看如何将其与参数一起应用,你会注意到 Y 永远不会返回,因为在它可以应用之前,(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)你可以通过应用替换规则来看到这一点。这里的线索是,这有效地停止了每一步的计算,直到它实际投入使用。