【问题标题】: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 表单创建的环境框架内。

            现在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 - 作为closure object。 IE。一个参数n 的函数,它还记住ghU 的绑定。

            所以当这个闭包被调用时,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)。请注意,在其主体中,它使用 fx 调用提供的 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
                  相关资源
                  最近更新 更多