Optimization 部分尾部递归函数是否仍能获得完全尾部递归函数的优化优势? 我意识到,对于不同的语言,这个问题的答案可能是不同的,我最感兴趣的语言是C++。如果因为无法以语言无关的方式回答而需要更改标记,请随意。 有没有可能让一个函数部分是尾部递归的,并且仍然可以获得尾部递归的好处

Optimization 部分尾部递归函数是否仍能获得完全尾部递归函数的优化优势? 我意识到,对于不同的语言,这个问题的答案可能是不同的,我最感兴趣的语言是C++。如果因为无法以语言无关的方式回答而需要更改标记,请随意。 有没有可能让一个函数部分是尾部递归的,并且仍然可以获得尾部递归的好处,optimization,language-agnostic,partial,tail-recursion,Optimization,Language Agnostic,Partial,Tail Recursion,据我所知,尾部递归是这样的:编译器将优化函数,而不是执行完整的函数调用,从而将参数更改为新参数,并跳到函数的开头 如果您有这样一个函数: def example(arg): if arg == 0: return 0 # base case if arg % 2 == 0: return example(arg - 1) # should be tail recursive return 3 + example(arg - 1) # i

据我所知,尾部递归是这样的:编译器将优化函数,而不是执行完整的函数调用,从而将参数更改为新参数,并跳到函数的开头

如果您有这样一个函数:

def example(arg):
    if arg == 0:
        return 0 # base case

    if arg % 2 == 0:
        return example(arg - 1) # should be tail recursive

    return 3 + example(arg - 1) # isn't tail recursive because 3 is added to the result

当一个乐观主义者遇到类似的情况时(函数在某些情况下是尾部递归的,而在其他情况下不是),它会把一个变成
跳转
,另一个变成
调用
,还是会把一些优化现实的事实(如果我知道的话,我不会问)使它不得不将所有内容都转换为
调用
,并失去如果函数是尾部递归的,您将拥有的所有效率?

据我所知,智能编译器可以通过跳到示例入口点而不是设置新的堆栈框架,将尾部递归应用于您的第一次调用。下面的返回将向原始调用方展开堆栈,在一个步骤中有效地“结束”两个调用,即使它不能对另一个调用这样做

您可以通过在调用中添加3来优化函数:

def example(arg, add=0):
    arg += add
    ....
    return example(arg - 1, 3)  # tail now too
另一种技术是创建第二个函数,并让两者相互调用


我不知道Python或C++编译器是否可以处理,但是可以检查C++的程序集输出。奇怪的是,我认为检查python的字节码输出可能更难。

据我所知,智能编译器可以通过跳转到示例入口点而不是设置新的堆栈框架,将尾部递归应用于第一次调用。下面的返回将向原始调用方展开堆栈,在一个步骤中有效地“结束”两个调用,即使它不能对另一个调用这样做

您可以通过在调用中添加3来优化函数:

def example(arg, add=0):
    arg += add
    ....
    return example(arg - 1, 3)  # tail now too
另一种技术是创建第二个函数,并让两者相互调用


我不知道Python或C++编译器是否可以处理,但是可以检查C++的程序集输出。奇怪的是,我认为检查python的字节码输出可能更难。

在Scheme中,我想到尾部调用时想到的第一种语言,第二种情况是语言规范保证的尾部调用。(术语说明:最好将此类函数调用称为“尾部调用”。)

Scheme规范准确地定义了Scheme中的尾部调用,并要求编译器专门支持它们。您可以在11.20中看到定义。R6RS()的尾部调用和尾部上下文

请注意,在Scheme中,规范没有提到尾部调用的优化。相反,它说一个实现必须支持无限数量的活动尾部调用——这是语言运行时的语义属性。它们可以实现为普通调用,但通常不是

示例,用C:

以您的示例的C版本为例

int example(int arg)
{
    if (arg == 0)
        return 0;
    if ((arg % 2) == 0)
        return example(arg - 1);
    return 3 + example(arg - 1);
}
使用gcc针对i386的常用优化设置(
-O2
)编译它:

_example:
    pushl   %ebp
    xorl    %eax, %eax
    movl    %esp, %ebp
    movl    8(%ebp), %edx
    testl   %edx, %edx
    jne L5
    jmp L15
    .align 4,0x90
L14:
    decl    %edx
    testl   %edx, %edx
    je  L7
L5:
    testb   $1, %dl
    je  L14
    decl    %edx
    addl    $3, %eax
    testl   %edx, %edx
    jne L5
L7:
    leave
    ret
L15:
    leave
    xorl    %eax, %eax
    ret

请注意,汇编代码中没有函数调用。GCC不仅将尾部调用优化为跳转,还将非尾部调用优化为跳转。

在Scheme(我想到尾部调用时想到的第一种语言)中,语言规范保证第二种情况是尾部调用。(术语说明:最好将此类函数调用称为“尾部调用”。)

Scheme规范准确地定义了Scheme中的尾部调用,并要求编译器专门支持它们。您可以在11.20中看到定义。R6RS()的尾部调用和尾部上下文

请注意,在Scheme中,规范没有提到尾部调用的优化。相反,它说一个实现必须支持无限数量的活动尾部调用——这是语言运行时的语义属性。它们可以实现为普通调用,但通常不是

示例,用C:

以您的示例的C版本为例

int example(int arg)
{
    if (arg == 0)
        return 0;
    if ((arg % 2) == 0)
        return example(arg - 1);
    return 3 + example(arg - 1);
}
使用gcc针对i386的常用优化设置(
-O2
)编译它:

_example:
    pushl   %ebp
    xorl    %eax, %eax
    movl    %esp, %ebp
    movl    8(%ebp), %edx
    testl   %edx, %edx
    jne L5
    jmp L15
    .align 4,0x90
L14:
    decl    %edx
    testl   %edx, %edx
    je  L7
L5:
    testb   $1, %dl
    je  L14
    decl    %edx
    addl    $3, %eax
    testl   %edx, %edx
    jne L5
L7:
    leave
    ret
L15:
    leave
    xorl    %eax, %eax
    ret

请注意,汇编代码中没有函数调用。GCC不仅将尾部调用优化为跳转,还将非尾部调用优化为跳转。

是的,我知道我可以在无条件尾部递归的地方更改它,但我想给出一个我所说的示例。是的,我非常确信C++(至少MSVC++)可以将相互递归的函数转换为TAU-CAL。CPYTHON实现从来没有优化尾部调用,Guido已经开始讨论这个主题,称之为“unPythic”。看@Dietrich是的,我在尝试应该是尾部递归阶乘函数时注意到了这一点:)太糟糕了,guido太短视了。@Seth:请不要如此粗鲁地谈论语言设计师。尾部调用优化需要权衡,这不是一个明显的选择。是的,我知道我可以在无条件尾部递归的地方改变它,但我想给出一个我所说的例子。是的,我非常确信C++(至少MSVC++)可以将相互递归的函数转换为TAU-CAL。CPYTHON实现从来没有优化尾部调用,Guido已经开始讨论这个主题,称之为“unPythic”。看@Dietrich是的,我在尝试应该是尾部递归阶乘函数时注意到了这一点:)太糟糕了,guido太短视了。@Seth:请不要如此粗鲁地谈论语言设计师。助教