我在一个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)):究竟是做什么的?你在哪里输入你想要得到的阶乘值?
这不是为了上课,这只是出于好奇。
答案 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
个应用程序。
现在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
的环境框架内返回的closure object。即一个参数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
,然后{{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。
这里,
(lambda (f x) (f f x))
生成一个带两个参数的 lambda,第一个是 lambda(我们称之为 f
),第二个是参数(我们称之为 x
)。请注意,在其主体中,它使用 f
和 f
调用提供的 lambda x
。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的中央有一个巨大的黑洞。许多以前有生产力的程序员对这些黑洞的数学之美着迷,并且再也看不到了。