scala – 为什么在getOrElse中返回会导致尾递归不可能?

我对以下代码感到困惑:代码是人为的,但我认为它仍然是尾递归的.编译器不同意并产生错误消息:

@annotation.tailrec
def listSize(l : Seq[Any],s: Int = 0): Int = {
  if (l.isEmpty) {
    None.getorElse( return s )
  }
  listSize(l.tail,s + 1)
}

上面的代码如何使尾部回归不可能?为什么编译器会告诉我:

Could not optimize @tailrec annotated method listSize: it contains a recursive call not in tail position

类似的代码(在map中返回)编译很好:

@annotation.tailrec
def listSize(l : Seq[Any],s: Int = 0): Int = {
  if (l.isEmpty) {
    Some(()).map( return s )
  }
  listSize(l.tail,s + 1)
}

即使通过内联None.isEmpty获得的代码编译也很好:

@annotation.tailrec
def listSize(l : Seq[Any],s: Int = 0): Int = {
  if (l.isEmpty) {
    if (None.isEmpty) {
      return s
    } else None.get
  }
  listSize(l.tail,s + 1)
}

另一方面,编译器拒绝具有略微修改的映射的代码并产生错误

@annotation.tailrec
def listSize(l : Seq[Any],s: Int = 0): Int = {
  if (l.isEmpty) {
    Some(()).map( x => return s )
  }
  listSize(l.tail,s + 1)
}

解决方法

这是因为你的第一个片段中的返回是非本地片段(它嵌套在lambda中). Scala使用异常来编译非本地返回表达式,因此编译器会从以下代码中转换代码

@annotation.tailrec
def listSize(l : Seq[Any],s + 1)
}

与此类似的东西(使用scalac -Xprint:tailcalls编译):

def listSize2(l : Seq[Any],s: Int = 0): Int = {
  val key = new Object

  try {
    if (l.isEmpty) {
      None.getorElse {
        throw new scala.runtime.nonlocalReturnControl(key,0)
      }
    }

    listSize2(l.tail,s + 1)
  } catch {
    case e: scala.runtime.nonlocalReturnControl[Int @unchecked] =>
      if (e.key == key)
        e.value
      else
        throw e
  }
}

最后一点是,当包装在try / catch块中时,递归调用不是尾调用.基本上,这个受到限制的例子:

def self(a: Int): Int = {
  try {
    self(a)
  } catch {
    case e: Exception => 0
  }
}

类似于这,显然不是尾递归的:

def self(a: Int): Int = {
  if (self(a)) {
    // ...
  } else {
    // ...
  }
}

在某些特殊情况下,您可以对此进行优化(最多两个堆栈帧,如果不是一个),但似乎不存在适用于这种情况的通用规则.

此外,此代码段中的返回表达式不是非本地返回,这就是可以优化该函数的原因:

@annotation.tailrec
def listSize(l : Seq[Any],s: Int = 0): Int = {
  if (l.isEmpty) {
    // `return` happens _before_ map `is` called.
    Some(()).map( return s )
  }
  listSize(l.tail,s + 1)
}

上面的工作原因是,在Scala中,返回e是表达式,而不是语句.它的类型是nothing,它是所有东西的子类型,包括Unit => X,这是map的param所需的类型.虽然评估非常简单,但是在map执行之前从封闭函数返回e(显然在方法调用之前计算参数).这可能会令人困惑,因为您希望将map(return e)解析/解释为map(_ => return e),但事实并非如此.

相关文章

共收录Twitter的14款开源软件,第1页Twitter的Emoji表情 Tw...
Java和Scala中关于==的区别Java:==比较两个变量本身的值,即...
本篇内容主要讲解“Scala怎么使用”,感兴趣的朋友不妨来看看...
这篇文章主要介绍“Scala是一种什么语言”,在日常操作中,相...
这篇文章主要介绍“Scala Trait怎么使用”,在日常操作中,相...
这篇文章主要介绍“Scala类型检查与模式匹配怎么使用”,在日...