Scala 尾部递归:比较两种情况
为什么这是尾部递归:Scala 尾部递归:比较两种情况,scala,recursion,functional-programming,stack-overflow,Scala,Recursion,Functional Programming,Stack Overflow,为什么这是尾部递归: def navigate(myList : List[Int]) : (Int, List[Int]) = { def navigate(step: Int, offset: Int, myList: List[Int]): (Int, scala.List[Int]) = { if //some test and exit condition, then a definition of jump else navigate(step +
def navigate(myList : List[Int]) : (Int, List[Int]) = {
def navigate(step: Int, offset: Int, myList: List[Int]): (Int, scala.List[Int]) = {
if //some test and exit condition, then a definition of jump
else navigate(step + 1, offset + jump, myList)
}
navigate(0, 0, myList)
}
虽然这不是:
def navigate(myList : List[Int]) : (Int, List[Int]) = {
navigate(0, 0, myList)
}
def navigate(step: Int, offset: Int, myList: List[Int]): (Int, scala.List[Int]) = {
if //some test and exit condition, then a definition of jump
else navigate(step + 1, offset + jump, myList)
}
如果myList
很长,则第一种情况不会出现任何问题,而第二种情况会导致StackOverflowerError
还有,有没有办法告诉编译器应该编译后一种方法,以便递归不会增加堆栈?为了使方法符合尾部递归优化的条件,它必须:
- 是尾部递归的(duh!)
- 不使用
return
- 是
final
final
,那么它不是尾部递归的原因是“尾部递归”意味着“尾部调用自身”,但是如果该方法是虚拟的,那么您就无法知道它是尾部调用自身还是其重写版本。在编译时确定方法是否被重写需要进行类层次结构分析,这相当于解决停止问题…IOW是不可能的
还有,有没有办法告诉编译器应该编译后者,这样递归就不会增加堆栈
不,没有办法打开或关闭尾部递归优化。尾部递归的方法(当然,根据Scala语言规范对“尾部递归”的定义)总是经过优化的。任何不这样做的Scala实现都违反了Scala语言规范
但是,还有
scala.annotation.tailrec
注释,它保证如果使用此注释注释的方法不符合SLS的尾部递归定义,编译器将生成错误。为了使方法符合尾部递归优化的条件,它必须:
- 是尾部递归的(duh!)
- 不使用
return
- 是
final
final
,那么它不是尾部递归的原因是“尾部递归”意味着“尾部调用自身”,但是如果该方法是虚拟的,那么您就无法知道它是尾部调用自身还是其重写版本。在编译时确定方法是否被重写需要进行类层次结构分析,这相当于解决停止问题…IOW是不可能的
还有,有没有办法告诉编译器应该编译后者,这样递归就不会增加堆栈
不,没有办法打开或关闭尾部递归优化。尾部递归的方法(当然,根据Scala语言规范对“尾部递归”的定义)总是经过优化的。任何不这样做的Scala实现都违反了Scala语言规范
但是,还有
scala.annotation.tailrec
注释,它保证如果使用此注释注释的方法不符合SLS的尾部递归定义,编译器将生成错误。可能有一些用处,我从中得到的是,你可以用@tailrec
注释,如果它没有优化,你会得到一个编译器警告。另外,你可能会得到一个关于为什么它没有优化的详细通知。@ameer我已经试过了,但我得到了编译错误“用@tailrec”注释的方法不是私有的,也不是最终的(因此可以重写)“在我的情况下,这可能是好的,但一般来说可能不是……我猜第一个示例中的方法是私有的/最终的,因为范围(嵌入外部导航意味着它不会被修改)。您是否尝试过将第二个示例设置为私有/最终?将其设置为私有/最终意味着它不能像警告所说的那样被覆盖(例如,函数定义定义定义一次之后不能更改)。如果不是这样,则优化将无效,因为正在递归的函数的定义可能会更改,因此我们无法确保将递归函数调用更改为循环是安全的,并且函数调用需要保持不变。可能有一些用处,我从中得到的是您可以注释使用@tailrec
时,如果未优化,您将收到一条编译器警告。此外,您可能会收到一条关于未优化原因的详细通知。@ameer我已经尝试过,但我得到了编译错误“用'@tailrec'注释的方法不是私有的,也不是最终的(因此可以重写)”在我的例子中,这可能是好的,但一般来说可能不是……我猜第一个示例中的方法是私有/最终的,因为范围(嵌入外部导航意味着它不会被修改)。您是否尝试过将第二个示例设置为私有/最终?将其设置为私有/最终意味着它不能像警告所说的那样被覆盖(例如,函数定义定义定义一次之后不能更改)。如果不是这样,则优化将无效,因为正在递归的函数的定义可能会更改,因此我们无法确保将递归函数调用更改为循环是安全的,并且函数调用需要保持不变。谢谢!“你不知道它是tail调用自身还是重写版本的自身”谢谢,这正是我错过的。谢谢!”你不知道它是tail调用自身还是重写版本的自身“谢谢,这正是我错过的。