为什么scala @tailrec不能在Option.flatMap上使用?

问题描述 投票:4回答:3

在scala中,以下两个函数用于完全相同的目的:

@tailrec
final def fn(str: String): Option[String] = {
  Option(str).filter(_.nonEmpty).flatMap { v =>
    fn(v.drop(1))
  }
}

@tailrec
final def fn2(str: String): Option[String] = {
  Option(str).filter(_.nonEmpty) match {
    case None    => None
    case Some(v) => fn2(v.drop(1))
  }
}

但是@tailrec仅适用于第二种情况,在第一种情况下它将生成以下错误:

错误:无法优化@tailrec带注释的方法fn:它包含一个不在尾部位置的递归调用Option(str).filter(_。nonEmpty).flatMap {v =>

为什么会出现这个错误?为什么这两个代码生成不同种类的JVM字节码

scala monads tail-recursion
3个回答
4
投票

要使fn为尾递归,递归调用必须是函数中的最后一个操作。如果你将fn传递给另一个函数,例如flatMap,那么另一个函数在调用fn之后可以自由执行其他操作,因此编译器无法确定它是尾递归的。

在某些情况下,编译器可以检测到调用fn是另一个函数中的最后一个操作,但在一般情况下不是。这将依赖于该其他函数的特定实现,因此如果其他函数被更改,则tailrec注释可能变为无效,这是不期望的依赖性。


2
投票

特别针对最后一个问题:

为什么这两个代码生成不同种类的JVM字节码

因为在JVM上,不能保证运行时包含Option类的JAR与编译时看到的相同。这很好,因为即使是次要版本的库(包括标准Java和Scala库)也是不兼容的,并且您需要所有依赖项都使用它们的公共依赖项的相同次要版本。

如果该类没有合适的flatMap方法,你将获得AbstractMethodError,但Scala的语义要求必须调用其flatMap方法。因此编译器必须发出字节码才能实际调用该方法。

Kotlin通过使用inline functions和Scala 3 will support them too来解决这个问题,但我不知道它是否会在这种情况下使用它们。


1
投票

考虑以下:

List('a', 'b').flatMap(List(_,'g'))  //res0: List[Char] = List(a, g, b, g)

我似乎很明显,flatMap()正在进行一些内部后处理以实现该结果。 List('a','g')如何与List('b','g')结合?

© www.soinside.com 2019 - 2024. All rights reserved.