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

发布于 2024-12-09 06:28:00 字数 372 浏览 2 评论 0原文

我在一个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) (xx)):它到底有什么作用?您在哪里输入要获取阶乘的值?

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

I'm in a Scheme class and I was curious about writing a recursive function without using define. The main problem, of course, is that you cannot call a function within itself if it doesn't have a name.

I did find this example: It's a factorial generator using only lambda.

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

But I can't even make sense of the first call, (lambda (x) (x x)): What exactly does that do? And where do you input the value you want to get the factorial of?

This is not for the class, this is just out of curiosity.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

青柠芒果 2024-12-16 06:28:00

(lambda (x) (xx)) 是一个对自身调用参数 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) (xx)) ,给 x 一个对自身的引用,所以现在 x 可以引用自己,从而递归。

(lambda (x) (x x)) is a function that calls an argument, x, on itself.

The whole block of code you posted results in a function of one argument. You could call it like this:

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

That calls it with 5, and returns 120.

The easiest way to think about this at a high level is that the first function, (lambda (x) (x x)), is giving x a reference to itself so now x can refer to itself, and hence recurse.

深海夜未眠 2024-12-16 06:28:00

表达式 (lambda (x) (xx)) 创建一个函数,当使用一个参数(必须是函数)求值时,将该函数及其自身作为参数应用。

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

(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)))

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

The expression (lambda (x) (x x)) creates a function that, when evaluated with one argument (which must be a function), applies that function with itself as an argument.

Your given expression evaluates to a function that takes one numeric argument and returns the factorial of that argument. To try it:

(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)))

There are several layers in your example, it's worthwhile to work through step by step and carefully examine what each does.

当爱已成负担 2024-12-16 06:28:00

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

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

如果你不喜欢我的内容已经写过,只需用谷歌搜索一下 Y Combinator (函数)即可。

Basically what you have is a form similar to the Y combinator. If you refactored out the factorial specific code so that any recursive function could be implemented, then the remaining code would be the Y combinator.

I have gone through these steps myself for better understanding.
https://gist.github.com/z5h/238891

If you don't like what I've written, just do some googleing for Y Combinator (the function).

苦笑流年记忆 2024-12-16 06:28:00

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

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

如果您愿意,您应该阅读小阴谋家中的示例章节(第 9 章)可以跟随它。它讨论了如何构建这种类型的函数,并最终将此模式提取到 Y 组合器 (一般可用于提供递归)。

(lambda (x) (x x)) takes a function object, then invokes that object using one argument, the function object itself.

This is then called with another function, which takes that function object under the parameter name fact-gen. It returns a lambda that takes the actual argument, n. This is how the ((fact-gen fact-gen) (sub1 n)) works.

You should read the sample chapter (Chapter 9) from The Little Schemer if you can follow it. It discusses how to build functions of this type, and ultimately extracting this pattern out into the Y combinator (which can be used to provide recursion in general).

笔落惊风雨 2024-12-16 06:28:00

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

首先,我们知道这一点:

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

使用 letrec 我们可以递归地创建函数。我们看到,当我们调用 (fact 5) 时,fact 已经绑定到一个函数。如果我们还有另一个函数,我们可以这样称呼它(另一个事实5),现在另一个被称为二元函数(我的英文是不好,抱歉)。我们可以这样定义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 来调用它,其中case 函数 f 恰好是 fact 本身。

如果您掌握了以上所有内容,您现在就可以编写 Y 组合器,用 lambda 替换 let

I like this question. 'The scheme programming language' is a good book. My idea is from Chapter 2 of that book.

First, we know this:

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

With letrec we can make functions recursively. And we see when we call (fact 5), fact is already bound to a function. If we have another function, we can call it this way (another fact 5), and now another is called binary function (my English is not good, sorry). We can define another as this:

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

Why not we define fact this way?

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

If fact is a binary function, then it can be called with a function f and integer n, in which case function f happens to be fact itself.

If you got all the above, you could write Y combinator now, making a substitution of let with lambda.

极致的悲 2024-12-16 06:28:00

你可以这样定义它:

(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) 时,它会在 let 创建的环境框架内简化为 ((hh) 5) 应用程序代码> 形式。

现在,将 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 -作为闭包对象。即一个参数 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)))))))

(gg) 应用程序被简化为 (hh) 应用程序,因为 g 指向在创建闭包对象的环境之上的环境框架中定义的 h 。也就是说,在上面的 let 表单中。但我们已经看到了 (hh) 调用的减少,它创建了闭包,即一个参数 n 的函数,用作我们的 阶乘 函数,在下一次迭代中将使用 4 调用该函数,然后使用 3 等调用。

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

You define it like this:

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

which is how letrec really works. See LiSP by Christian Queinnec.


In the example you're asking about, the self-application combinator is called "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))

The subtlety here is that, because of let's scoping rules, the lambda expressions can not refer to the names being defined.

When ((U h) 5) is called, it is reduced to ((h h) 5) application, inside the environment frame created by the let form.

Now the application of h to h creates new environment frame in which g points to h in the environment above it:

(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))

The (lambda (n) ...) expression here is returned from inside that environment frame in which g points to h above it - as a closure object. I.e. a function of one argument, n, which also remembers the bindings for g, h, and U.

So when this closure is called, n gets assigned 5, and the if form is entered:

(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)))))))

The (g g) application gets reduced into (h h) application because g points to h defined in the environment frame above the environment in which the closure object was created. Which is to say, up there, in the top let form. But we've already seen the reduction of (h h) call, which created the closure i.e. the function of one argument n, serving as our factorial function, which on the next iteration will be called with 4, then 3 etc.

Whether it will be a new closure object or same closure object will be reused, depends on a compiler. This can have an impact on performance, but not on semantics of the recursion.

写给空气的情书 2024-12-16 06:28:00

对于单个 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 (fx) (ffx))是一个带有两个参数的 lambda,第一个是 lambda(我们称之为 f),第二个是参数(我们称之为 x)。请注意,在其主体中,它以 fx 作为参数调用提供的 lambda f - 相同 f
  2. 现在,lambda f (来自第 1 点)即 self 是我们想要用来递归的。看,当递归调用 self 时,我们还传递 self 作为第一个参数,将 (- n 1) 作为第二个参数。
    因此,调用 (self self ...) 使用与 (ff ...) 启动的相同组合 - 这就是递归的含义,即从 self 调用 selfself 绑定到第二个 lambda (lambda (self ...) ...) 并作为其体内的值传递给 self
    这个传递的值在新调用中用作函数及其参数,克服了在 lambda 演算中无法为函数提供全局名称并且只能使用无名(“匿名”)lambda 的限制。

With a single lambda it's not possible. But using two or more lambda's it is possible. As, all other solutions are using three lambdas or let/letrec, I'm going to explain the method using two lambdas:

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

And the output is 120.

Here,

  1. (lambda (f x) (f f x)) is a lambda that takes two arguments, the first one is a lambda (let's call it f) and the second is the parameter (let's call it x). Notice, in its body it calls the provided lambda f with f and x as arguments – the same f.
  2. Now, lambda f (from point 1) i.e. self is what we want to use to recurse. See, when calling self recursively, we also pass self as the first argument and (- n 1) as the second argument.
    Thus calling (self self ...) uses the same combination as initiated by (f f ...) – which is the meaning of recursion, i.e. calling self from self: self becomes bound to the second lambda (lambda (self ...) ...) and is passed to self as a value inside its body.
    This passed value being used both as a function in the new call and its argument overcomes the limitation of not being able to give global names to functions, in the lambda calculus, and only using nameless ("anonymous") lambdas.
沉睡月亮 2024-12-16 06:28:00

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

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

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

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

I was curious about writing a recursive function without using define.
The main problem, of course, is that you cannot call a function within
itself if it doesn't have a name.

A little off-topic here, but seeing the above statements I just wanted to let you know that "without using define" does not mean "doesn't have a name". It is possible to give something a name and use it recursively in Scheme without define.

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

It would be more clear if your question specifically says "anonymous recursion".

素年丶 2024-12-16 06:28:00

我发现这个问题是因为我需要在宏中使用递归辅助函数,而不能使用定义。

人们想要理解 (lambda (x) (xx)) 和 Y 组合器,但命名为 let 可以完成工作,而不会吓跑游客:

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

人们也可以推迟理解 (lambda ( x) (xx)) 和 Y 组合器,如果这样的代码就足够了。与哈斯克尔和银河系一样,Scheme 的中心也有一个巨大的黑洞。许多以前富有成效的程序员被这些黑洞的数学之美所吸引,然后就再也没有出现过。

I found this question because I needed a recursive helper function inside a macro, where one can't use define.

One wants to understand (lambda (x) (x x)) and the Y-combinator, but named let gets the job done without scaring off tourists:

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

One can also put off understanding (lambda (x) (x x)) and the Y-combinator, if code like this suffices. Scheme, like Haskell and the Milky Way, harbors a massive black hole at its center. Many a formerly productive programmer gets entranced by the mathematical beauty of these black holes, and is never seen again.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文