Recursion 在Scheme中,如何使用lambda创建递归函数?
我在Scheme类中,我很好奇不使用define编写递归函数。当然,主要的问题是,如果函数本身没有名称,就无法调用函数本身 我确实找到了这个例子:它是一个只使用lambda的阶乘生成器Recursion 在Scheme中,如何使用lambda创建递归函数?,recursion,lambda,scheme,anonymous-recursion,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))))))) 但是我甚至不能理解第一个电话,(lambd
((lambda (x) (x x))
(lambda (fact-gen)
(lambda (n)
(if (zero? n)
1
(* n ((fact-gen fact-gen) (sub1 n)))))))
但是我甚至不能理解第一个电话,(lambda(x)(x)):那到底是做什么的?在哪里输入想要得到阶乘的值
这不是针对类的,这只是出于好奇。(lambda(x)(x))
获取函数对象,然后使用一个参数调用该对象,即函数对象本身
然后使用另一个函数调用该函数,该函数将该函数对象置于参数名fact gen
下。它返回一个接受实际参数的lambda,n
。这就是((fact gen fact gen)(sub1 n))
的工作原理
如果你能理解的话,你应该阅读本书的样本章节(第9章)。它讨论了如何构建这种类型的函数,并最终将此模式提取到中(通常可用于提供递归)。表达式(lambda(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)))
在您的示例中有几个层,值得一步一步地进行,并仔细检查每个层的作用。(lambda(x)(x))
是一个在自身上调用参数x的函数
您发布的整个代码块产生一个参数函数。你可以这样称呼它:
(((lambda (x) (x x))
(lambda (fact-gen)
(lambda (n)
(if (zero? n)
1
(* n ((fact-gen fact-gen) (sub1 n)))))))
5)
(let ((fact #f))
(set! fact
(lambda (n) (if (< n 2) 1
(* n (fact (- n 1))))))
(fact 5))
用5来调用它,返回120
在高层次上考虑这一点最简单的方法是,第一个函数,(lambda(x)(x))
给x一个自身的引用,因此现在x可以引用自身,从而递归
我对不使用define编写递归函数感到好奇。
当然,主要的问题是,您不能在内部调用函数
如果它没有名字的话
这里有点离题,但是看到上面的陈述,我只是想让你知道“不使用define”并不意味着“没有名字”。可以给某个对象命名,并在Scheme中递归使用它,而无需定义
(letrec
((fact
(lambda (n)
(if (zero? n)
1
(* n (fact (sub1 n)))))))
(fact 5))
如果你的问题特别提到“匿名递归”,那就更清楚了。基本上你所拥有的是一种类似于Y组合符的形式。如果您重构了特定于阶乘的代码,以便实现任何递归函数,那么剩下的代码将是Y组合器 为了更好地理解,我自己已经完成了这些步骤。
如果你不喜欢我写的东西,就在谷歌上搜索一下Y Combinator(函数)。我喜欢这个问题。”《scheme编程语言》是一本好书。我的想法来自那本书的第二章 首先,我们知道:
(letrec ((fact (lambda (n) (if (= n 1) 1 (* (fact (- n 1)) n))))) (fact 5))
使用letrec
我们可以递归地生成函数。当我们调用(fact 5)
时,我们看到,fact
已经绑定到一个函数。如果我们有另一个函数,我们可以这样叫它(另一个事实5)
,现在另一个叫做二进制函数(对不起,我的英语不好)。我们可以将另一个
定义为:
(let ((another (lambda (f x) .... (f x) ...))) (another fact 5))
我们为什么不这样定义事实呢
(let ((fact (lambda (f n) (if (= n 1) 1 (* n (f f (- n 1))))))) (fact fact 5))
如果fact
是一个二进制函数,那么可以使用函数f
和整数n
调用它,在这种情况下,函数f
恰好是fact
本身
如果您具备以上所有条件,现在就可以编写Ycombinator,将
替换为let
与lambda
您可以这样定义它:
(((lambda (x) (x x))
(lambda (fact-gen)
(lambda (n)
(if (zero? n)
1
(* n ((fact-gen fact-gen) (sub1 n)))))))
5)
(let ((fact #f))
(set! fact
(lambda (n) (if (< n 2) 1
(* n (fact (- n 1))))))
(fact 5))
这里的微妙之处在于,由于let
的作用域规则,lambda表达式不能引用正在定义的名称
当调用((uh)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
,它还记住了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)
应用程序被缩减为(h)
应用程序,因为g
指向创建闭包对象的环境上方的环境框架中定义的h
。也就是说,在上面,在顶部的let
表单中。但是我们已经看到了(hh)
调用的减少,它创建了闭包,即一个参数n
的函数,作为我们的阶乘
函数,在下一次迭代中将使用4
调用它,然后使用3
等等
它是一个新的闭包对象,还是重复使用同一个闭包对象,取决于编译器。这可能会影响性能,但不会影响递归的语义。我发现这个问题是因为我需要宏中的递归助手函数,而宏中不能使用define
有人想了解(lambda(x)(x x))
和Y-combinator,但名为let的人在不吓跑游客的情况下完成了任务: