尾递归更高效,因为它重复使用同一堆栈帧而不是创建一个新的,但为什么在scheme中这对所有内容都是必需的呢?
尾递归更高效,因为它重复使用同一堆栈帧而不是创建一个新的,但为什么在scheme中这对所有内容都是必需的呢?
goto
,因此所有的循环最终都是使用尾递归完成的。如果没有完备的尾递归保证,就没有可靠的方式来提供Scheme中的循环。
do
宏,因为@newacct提到了它。例如:(do ((i 1 (+ i 1)))
((> i 10))
(display i)
(newline))
正如我所提到的,Scheme 没有 goto
,因此它必须从其他地方获取循环。它实际上是通过宏展开得到(类似于)这个:
(let loop ((i 1))
(unless (> i 10)
(display i)
(newline)
(loop (+ i 1))))
loop
并不是关键字或内置函数。它是通过命名的let
†创建的命名函数,并在unless
表单底部通过尾递归调用。let
(口语上‡)的宏展开:(letrec ((loop (lambda (i)
(unless (> i 10)
(display i)
(newline)
(loop (+ i 1))))))
(loop 1))
严格来说,它会被宏展开为:
((letrec ((loop (lambda (i)
(unless (> i 10)
(display i)
(newline)
(loop (+ i 1))))))
loop)
1)
do
,但很少使用。 - newacctdo
不使用 goto
。它会在命名的 let
上进行尾递归的宏展开。 - C. K. Youngcall/cc
,它是“增强版”的goto
。事实上,这可能是真正的原因 - 没有TR保证,call/cc
是无法使用的。 - Will Nessdo
实现为内置语法而不是宏,但我无法想象任何一种实现会想要这样做,因为:1. 命名的let
比do
严格更通用(所有do
实例都可以重写为命名的let
,但反之则不行),2.do
很少使用。因此,在实践中,您确实需要适当的尾递归来循环,除非您想滥用call/cc
来实现该目的。 - C. K. Young没有TR的保证,call/cc
将无法使用。循环本身可以通过do
(它是语言的一部分)来实现。
编辑:这被证明是不正确的。请参见John Clements在问题中的评论。一种语言可以具有非TR函数调用机制,但具有单独的特殊机制来调用连续体。
call/cc
。 - Will Nesscall/cc
应该会倒回调用堆栈;但是没有TR,对函数的调用将保留其调用堆栈。因此,即使您允许它,也会出现堆栈溢出。这就是我的想法。 - Will Ness