27

我在一个 Scheme 类中,我很好奇在不使用定义的情况下编写递归函数。当然,主要问题是,如果函数没有名称,则不能在其内部调用函数。

我确实找到了这个例子:它是一个只使用 lambda 的阶乘生成器。

((lambda (x) (x x))
 (lambda (fact-gen)
   (lambda (n)
     (if (zero? n)
         1
         (* n ((fact-gen fact-gen) (sub1 n)))))))

但我什至无法理解第一次调用,(lambda (x) (xx)):这到底是做什么的?你在哪里输入你想要得到阶乘的值?

这不是为了上课,这只是出于好奇。

4

9 回答 9

17

(lambda (x) (x x))是一个在自身上调用参数x的函数。

您发布的整个代码块导致一个参数的函数。你可以这样称呼它:

(((lambda (x) (x x))
  (lambda (fact-gen)
    (lambda (n)
      (if (zero? n)
          1
          (* n ((fact-gen fact-gen) (sub1 n)))))))
 5)

用 5 调用它,并返回 120。

在高层次上考虑这一点的最简单方法是,第一个函数(lambda (x) (x x))是给x一个对自身的引用,所以现在x可以引用自己,因此是递归的。

于 2011-10-10T21:53:22.033 回答
11

该表达式(lambda (x) (x x))创建一个函数,当使用一个参数(必须是一个函数)进行评估时,该函数将该函数本身作为参数应用。

您给定的表达式计算为一个函数,该函数接受一个数字参数并返回该参数的阶乘。尝试一下:

(let ((factorial ((lambda (x) (x x))
                  (lambda (fact-gen)
                    (lambda (n)
                      (if (zero? n)
                          1
                          (* n ((fact-gen fact-gen) (sub1 n)))))))))
  (display (factorial 5)))

您的示例中有几个层次,值得一步一步完成并仔细检查每个层次的作用。

于 2011-10-10T21:51:45.217 回答
6

基本上你所拥有的是一种类似于 Y 组合器的形式。如果您重构出特定于阶乘的代码,以便可以实现任何递归函数,那么剩余的代码将是 Y 组合子。

为了更好地理解,我自己已经完成了这些步骤。
https://gist.github.com/z5h/238891

如果您不喜欢我写的内容,只需对 Y Combinator(函数)进行一些谷歌搜索。

于 2011-10-11T05:52:09.407 回答
5

(lambda (x) (x x))接受一个函数对象,然后使用一个参数调用该对象,即函数对象本身。

然后用另一个函数调用它,该函数在参数 name 下获取该函数对象fact-gen。它返回一个接受实际参数的 lambda n。这就是((fact-gen fact-gen) (sub1 n))工作原理。

如果您可以阅读The Little Schemer的示例章节(第 9 章),您应该阅读它。它讨论了如何构建这种类型的函数,并最终将这种模式提取到Y 组合器中(通常可用于提供递归)。

于 2011-10-10T21:51:37.713 回答
5

你这样定义它:

(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,它还记住gh和的绑定U

所以当这个闭包被调用时,n被赋值5if输入表单:

(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等等。

无论是新的闭包对象还是相同的闭包对象将被重用,取决于编译器。这可能会影响性能,但不会影响递归的语义。

于 2012-08-06T17:27:35.317 回答
4

我喜欢这个问题。《方案编程语言》是一本好书。我的想法来自那本书的第 2 章。

首先,我们知道:

(letrec ((fact (lambda (n) (if (= n 1) 1 (* (fact (- n 1)) n))))) (fact 5))

有了letrec我们可以递归地制作函数。我们看到当我们调用(fact 5), fact已经绑定到一个函数。如果我们还有别的函数,可以这样调用(another fact 5),现在another二元函数(我的英文不好,不好意思)。我们可以another这样定义:

(let ((another (lambda (f x) .... (f x) ...))) (another fact 5))

我们为什么不这样定义fact呢?

(let ((fact (lambda (f n) (if (= n 1) 1 (* n (f f (- n 1))))))) (fact fact 5))

如果fact二元函数,则可以用函数f和整数调用它n,在这种情况下,函数f恰好是fact它自己。

如果您具备以上所有条件,您现在可以编写Y组合器,let用 with替换lambda

于 2012-08-06T08:11:38.380 回答
3

使用单个 lambda 是不可能的。但是使用两个或更多的 lambda 是可能的。因为,所有其他解决方案都使用三个 lambda 或 let/letrec,所以我将使用两个 lambda 来解释该方法:

((lambda (f x)
   (f f x))
 (lambda (self n)
   (if (= n 0)
       1
       (* n (self self (- n 1)))))
 5)

输出为 120。

这里,

  1. (lambda (f x) (f f x))生成一个带有两个参数的 lambda,第一个是 lambda(让我们调用它f),第二个是参数(让我们调用它x)。请注意,在其主体中,它f使用fand调用提供的 lambda x
  2. 现在, lambda f(从第 1 点开始) ieself就是我们想要递归的。看,当self递归调用时,我们也self作为第一个参数和(- n 1)第二个参数传递。
于 2021-02-12T03:32:26.760 回答
2

我很好奇在不使用定义的情况下编写递归函数。当然,主要问题是,如果函数没有名称,则不能在其内部调用函数。

这里有点题外话,但是看到上面的陈述我只是想让你知道“不使用定义”并不意味着“没有名字”。可以在没有定义的情况下在 Scheme 中给某个东西命名并递归地使用它。

(letrec
  ((fact
     (lambda (n)
       (if (zero? n)
         1
         (* n (fact (sub1 n)))))))
  (fact 5))

如果您的问题专门说“匿名递归”,那就更清楚了。

于 2011-10-10T23:42:06.443 回答
1

我发现了这个问题,因为我需要一个宏内的递归辅助函数,其中不能使用定义。

一个想要理解(lambda (x) (x x))和 Y 组合子的人,但命名为 let 可以在不吓跑游客的情况下完成工作:

 ((lambda (n)
   (let sub ((i n) (z 1))
     (if (zero? i)
         z
         (sub (- i 1) (* z i)) )))
 5 )

如果这样的代码足够的话,也可以推迟理解(lambda (x) (x x))和 Y 组合器。计划,就像哈斯克尔和银河系一样,在其中心有一个巨大的黑洞。许多以前富有成效的程序员被这些黑洞的数学之美迷住了,再也见不到了。

于 2019-01-25T06:26:35.977 回答