【问题标题】:In Scheme, how do you use lambda to create a recursive function?在 Scheme 中,如何使用 lambda 创建递归函数?
【发布时间】:2011-12-04 20:42:08
【问题描述】:

我在一个 Scheme 类中,我很想编写一个不使用定义的递归函数。当然,主要问题是如果函数没有名称,您就无法在其内部调用函数。

我确实找到了这个例子:它是一个只使用 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)):这到底是做什么的?你在哪里输入你想要得到阶乘的值?

这不是针对班级的,只是出于好奇。

【问题讨论】:

    标签: recursion lambda scheme anonymous-recursion


    【解决方案1】:

    (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 可以引用到自己,因此递归。

    【讨论】:

      【解决方案2】:

      表达式(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)))
      

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

      【讨论】:

        【解决方案3】:

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

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

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

        【讨论】:

          【解决方案4】:

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

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

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

          【讨论】:

            【解决方案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 表单创建的环境框架内。

            现在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 - 作为closure object。 IE。一个参数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等

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

            【讨论】:

              【解决方案6】:

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

              首先,我们知道:

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

              使用letrec,我们可以递归地创建函数。我们看到当我们调用(fact 5) 时,fact 已经绑定到一个函数。如果我们还有另一个函数,我们可以这样称呼它(another fact 5),现在another被称为binary函数(我的英文不好,抱歉)。我们可以这样定义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 替换为lambda。

              【讨论】:

              • 事实上,最后一个变体正是有问题的代码,uncurried。 IOW,OP 代码是这里最后一个变体的柯里化版本,将一个二进制函数变成两个嵌套的单参数lambdas,写成((f f) x) 而不是(f f x)。
              【解决方案7】:

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

              【讨论】:

                【解决方案8】:

                我很好奇在不使用定义的情况下编写递归函数。 当然,主要问题是您不能在其中调用函数 如果它没有名字,它自己。

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

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

                如果您的问题具体说“匿名递归”会更清楚。

                【讨论】:

                • 是的,但是由于 let 本身就是 lambda 表达式的别名,因此能够给出这仍然是有意义的lambda 表达式,在它自身内部使用的本地名称。我相信这就是 OP 想要的,但我不知道如何在 Scheme 中做到这一点。
                • @Hibou57:虽然let 很容易转换为 lambda 表达式应用程序,但 letrec 有点难做。
                • @Hibou57 (letrec ((name (lambda-expression))) (body)) = (let ((name (Y (lambda (name) (lambda-expression))))) (body))。 or something。 -- 另一种方式是with set!:(let ((name #f)) (set! name (lambda-expression)) (body))。
                【解决方案9】:

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

                想了解 (lambda (x) (x x)) 和 Y-combinator,但命名为 let 可以在不吓跑游客的情况下完成工作:

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

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

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-07-08
                  • 2013-10-05
                  • 2013-01-06
                  • 2016-06-26
                  • 1970-01-01
                  相关资源
                  最近更新 更多