在 Option.getOrElse 上断言 @tailrec

发布于 2024-11-04 18:29:14 字数 1221 浏览 0 评论 0原文

在下面的代码中,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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

无所的.畏惧 2024-11-11 18:29:14

这不是错误,不是疏忽,也不是尾递归。

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

maybeNext.map{rec].getOrElse(n)

首先,最后一次调用是 getOrElse(n)。这个调用不是可选的——它总是被调用,并且有必要调整结果。但让我们忽略这一点。

倒数第二个调用是 map{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)                 
}

这里的 goto 不在 rec 内。它位于匿名 Function1apply 内部,而后者又位于 Optionmap 内部>,因此这里的分支会在每次调用时留下两个堆栈帧。首先假设方法间分支是可能的。

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:

maybeNext.map{rec].getOrElse(n)

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 to rec. 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 on map 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:

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
   }                         
}

How would that happen in the other code?

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)                 
}

The goto here is not inside rec. It is inside an anonymous Function1's apply, which, by its turn, is inside an Option's map, so a branch here would leave two stack frames on each call. Assuming inter-method branching was possible in first place.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文