在Option.getOrElse上断言@tailrec

时间:2011-05-04 00:05:37

标签: scala tail-recursion

在下文中,行maybeNext.map{rec}.getOrElse(n)使用Option monad来实现递归或转义模式。

scala> @tailrec                                          
     | def rec(n: Int): Int = {                          
     |   val maybeNext = if (n >= 99) None else Some(n+1)
     |   maybeNext.map{rec}.getOrElse(n)                 
     | }

看起来不错:

<console>:7: error: could not optimize @tailrec annotated method: 
it contains a recursive call not in tail position
       def rec(n: Int): Int = {
           ^

我认为编译器应该能够在这种情况下对尾递归进行排序。它等同于以下(有些令人厌恶但可编译)的样本:

scala> @tailrec                                          
     | def rec(n: Int): Int = {                          
     |   val maybeNext = if (n >= 99) None else Some(n+1)
     |   if (maybeNext.isEmpty) n                        
     |   else rec(maybeNext.get)                         
     | }
rec: (n: Int)Int

这里有人能提供照明吗?为什么编译器无法解决这个问题?这是一个错误,还是一个疏忽?问题太难了吗?

编辑:从第一个示例中删除@tailrec并编译方法;循环终止。最后一次通话始终为getOrElse,相当于if option.isEmpty defaultValue else recurse。我认为这可以而且应该由编译器推断。

1 个答案:

答案 0 :(得分:8)

这不是一个错误,它不是一个疏忽,它不是一个尾递归。

是的,您可以以尾递归方式编写代码,但这并不意味着每个等效算法都可以进行尾递归。我们来看看这段代码:

maybeNext.map{rec].getOrElse(n)

首先,最后一次通话是getOrElse(n)。此调用不是可选的 - 它始终是,并且必须调整结果。但是我们忽略它。

倒数第二个电话是map{rec}rec。实际上,rec在您的代码中并未被称为 !其他一些函数调用它(事实上,它不是map上的最后一次调用),但不是你的函数。

对于尾部递归的东西,你需要能够用“goto”替换这个调用,可以这么说。像这样:

def rec(n: Int): Int = { 
  BEGINNING:                         
  val maybeNext = if (n >= 99) None else Some(n+1)
  if (maybeNext.isEmpty) n                        
  else { 
    n = maybeNext.get
    goto BEGINNING
   }                         
}

如何在其他代码中发生这种情况?

def rec(n: Int): Int = {                  
  BEGINNING:        
  val maybeNext = if (n >= 99) None else Some(n+1)
  maybeNext.map{x => n = x; goto BEGINNING}.getOrElse(n)                 
}

此处的转到<{1>}内的不是。它位于匿名rec的{​​{1}}内,而它位于Function1的{​​{1}}内,因此此处的分支会留下两个堆栈帧每次通话。假设首先可以进行方法间分支。