在 Option.getOrElse 上断言 @tailrec
在下面的代码中,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
。我认为这可以而且应该由编译器推断出来。
In the following, the line maybeNext.map{rec}.getOrElse(n)
uses the Option
monad to implement the recurse or escape pattern.
scala> @tailrec
| def rec(n: Int): Int = {
| val maybeNext = if (n >= 99) None else Some(n+1)
| maybeNext.map{rec}.getOrElse(n)
| }
Looks good, however:
<console>:7: error: could not optimize @tailrec annotated method:
it contains a recursive call not in tail position
def rec(n: Int): Int = {
^
I feel that the compiler should be able to sort out tail recursion in this case. It is equivalent to the following (somewhat repulsive, but compilable) sample:
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
Can anyone provide illumination here? Why can't the compiler figure it out? Is it a bug, or an oversight? Is the problem too difficult?
Edit: Remove the @tailrec
from the first example and the method compiles; the loop terminates. The last call is always getOrElse
which is equivalent to if option.isEmpty defaultValue else recurse
. I think this could and should be inferred by the compiler.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
这不是错误,不是疏忽,也不是尾递归。
是的,您可以以尾递归方式编写代码,但这并不意味着每个等效算法都可以成为尾递归。让我们看一下这段代码:
首先,最后一次调用是
getOrElse(n)
。这个调用不是可选的——它总是被调用,并且有必要调整结果。但让我们忽略这一点。倒数第二个调用是
map{rec}
。 不到记录
。事实上,您的代码中根本没有调用rec
!其他一些函数会调用它(事实上,它也不是map
上的最后一个调用),但不是您的函数。可以这么说,对于尾递归的东西,您需要能够用“goto”替换调用。像这样:
在其他代码中会如何发生?
这里的 goto 不在
rec
内。它位于匿名Function1
的apply
内部,而后者又位于Option
的map
内部>,因此这里的分支会在每次调用时留下两个堆栈帧。首先假设方法间分支是可能的。It is not a bug, it is not an oversight, and it is not a tail recursion.
Yes, you can write the code in a tail recursive manner, but that doesn't mean every equivalent algorithm can be made tail recursive. Let's take this code:
First, the last call is to
getOrElse(n)
. This call is not optional -- it is always made, and it is necessary to adjust the result. But let's ignore that.The next to last call is to
map{rec}
. Not torec
. In fact,rec
is not called at all in your code! Some other function calls it (and, in fact, it is not the last call onmap
either), but not your function.For something to be tail recursive, you need to be able to replace the call with a "goto", so to speak. Like this:
How would that happen in the other code?
The goto here is not inside
rec
. It is inside an anonymousFunction1
'sapply
, which, by its turn, is inside anOption
'smap
, so a branch here would leave two stack frames on each call. Assuming inter-method branching was possible in first place.