在Scheme中,如何使用lambda创建递归函数?

时间:2011-10-10 21:37:43

标签: recursion lambda scheme anonymous-recursion

我在一个Scheme类中,我很好奇在不使用define的情况下编写递归函数。当然,主要的问题是,如果没有名称,你就无法调用自身内的函数。

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

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

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

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

9 个答案:

答案 0 :(得分:15)

(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 可以参考自己,然后递归。

答案 1 :(得分: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)))

您的示例中有多个图层,值得逐步完成并仔细检查每个图层的作用。

答案 2 :(得分:5)

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

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

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

答案 3 :(得分:5)

您可以这样定义:

(let ((fact #f)) 
  (set! fact 
        (lambda (n) (if (< n 2) 1 
                               (* n (fact (- n 1)))))) 
  (fact 5))

这是letrec真正起作用的方式。见Christian Queinnec的LiSP


在您要问的示例中,自应用组合器称为"U combinator"

(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个应用程序。

现在hh的应用创建了新的环境框架,其中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的环境框架内返回的closure object。即一个参数g的函数,它还会记住hUn的绑定。

因此,当调用此闭包时,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,然后{{1}}等

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

答案 4 :(得分:4)

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

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

如果你不喜欢我写的东西,只需为Y Combinator(函数)做一些googleing。

答案 5 :(得分: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调用它,在这种情况下函数{{1} }恰好是f本身。

如果您已完成上述所有操作,则可以立即编写 Y 组合器,并使用fact替换let

答案 6 :(得分:2)

  

我很好奇在不使用define的情况下编写递归函数。   当然,主要问题是你无法调用其中的函数   本身,如果它没有名字。

这里有点偏离主题,但看到上述陈述我只是想让你知道“不使用define”并不意味着“没有名字”。可以给一些名称并在Scheme中递归使用它而不用定义。

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

如果你的问题具体说“匿名递归”,那就更清楚了。

答案 7 :(得分:2)

使用单个 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)。请注意,在其主体中,它使用 ff 调用提供的 lambda x
  2. 现在,lambda f(从第 1 点开始)即 self 是我们想要递归的。请看,当递归调用 self 时,我们还将 self 作为第一个参数传递,(- n 1) 作为第二个参数传递。

答案 8 :(得分:0)

我发现了这个问题,因为我需要在宏中使用一个递归帮助器函数,而该宏不能使用define。

一个人想了解(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组合器的理解。像Haskell和银河系一样,Scheme的中央有一个巨大的黑洞。许多以前有生产力的程序员对这些黑洞的数学之美着迷,并且再也看不到了。