有没有办法关闭Scala编译器的尾递归优化?

Is there any way to turn off tail recursion optimization of Scala Compiler?

由于某些特殊原因,我想在一个大程序中删除所有@tailrec的效果,但不想手动这样做,编译时是否有任何可选参数可以关闭尾递归优化?我只想在代码中保留@tailrec,但不想检查它并在编译时进行尾递归优化。这可能吗?

@tailrec 注解没有开启 on/off 尾递归优化。如果带注释的例程不是正确尾递归,它只会告诉编译器出错。

如果例程是尾递归的,那么无论是否注释,优化都在那里。

来自docs

A method annotation which verifies that the method will be compiled with tail call optimization.

If it is present, the compiler will issue an error if the method cannot be optimized into a loop.

关键词是"verifies"。

更新

@mikołak 是正确的。快速检查显示 -g:notailcalls 标志关闭注释验证。

%%> scalac tailrec.scala 
tailrec.scala:5: error: could not optimize @tailrec annotated method rf: it contains a recursive call not in tail position
def rf(x: Int): Int = if (x < 1) x else rf(x-2)+1
                                               ^
one error found
%%> scalac -g:notailcalls tailrec.scala 
%%>

您可以使用编译器的 -g:notailcalls 选项。

来自文档:

-g:{none,source,line,vars,notailcalls}

"none" generates no debugging info,

"source" generates only the source file attribute,

"line" generates source and line number information,

"vars" generates source, line number and local variable information,

"notailcalls" generates all of the above and will not perform tail call optimization.

请注意,如文档所述,这还将启用调试信息生成


一个例子:

import scala.annotation.tailrec

class A {

@tailrec
final def x(i: Int): Int = if(i == 0) {i;} else {x(i-1)}

}
编译时

javap -p -v A.class of x "normally":

public final int x(int);
    descriptor: (I)I
    flags: ACC_PUBLIC, ACC_FINAL
    Code:
      stack=2, locals=2, args_size=2
         0: iload_1
         1: iconst_0
         2: if_icmpne     7
         5: iload_1
         6: ireturn
         7: iload_1
         8: iconst_1
         9: isub
        10: istore_1
        11: goto          0

相同,当用-g:notailcalls编译时:

public final int x(int);
    descriptor: (I)I
    flags: ACC_PUBLIC, ACC_FINAL
    Code:
      stack=3, locals=2, args_size=2
         0: iload_1
         1: iconst_0
         2: if_icmpne     9
         5: iload_1
         6: goto          16
         9: aload_0
        10: iload_1
        11: iconst_1
        12: isub
        13: invokevirtual #12                 // Method x:(I)I
        16: ireturn

重要的一点是位置 13 上的 invokevirtual


请注意,顺便说一下,jwvh 在 @tailrec 的插话中谨慎地纠正了您 - 它实际上并没有切换尾部优化,它唯一做的就是通知编译器失败如果它无法插入尾调用优化,它无论如何都会尝试