在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字节码
要使fn
为尾递归,递归调用必须是函数中的最后一个操作。如果你将fn
传递给另一个函数,例如flatMap
,那么另一个函数在调用fn
之后可以自由执行其他操作,因此编译器无法确定它是尾递归的。
在某些情况下,编译器可以检测到调用fn
是另一个函数中的最后一个操作,但在一般情况下不是。这将依赖于该其他函数的特定实现,因此如果其他函数被更改,则tailrec
注释可能变为无效,这是不期望的依赖性。
特别针对最后一个问题:
为什么这两个代码生成不同种类的JVM字节码
因为在JVM上,不能保证运行时包含Option
类的JAR与编译时看到的相同。这很好,因为即使是次要版本的库(包括标准Java和Scala库)也是不兼容的,并且您需要所有依赖项都使用它们的公共依赖项的相同次要版本。
如果该类没有合适的flatMap
方法,你将获得AbstractMethodError
,但Scala的语义要求必须调用其flatMap
方法。因此编译器必须发出字节码才能实际调用该方法。
Kotlin通过使用inline
functions和Scala 3 will support them too来解决这个问题,但我不知道它是否会在这种情况下使用它们。
考虑以下:
List('a', 'b').flatMap(List(_,'g')) //res0: List[Char] = List(a, g, b, g)
我似乎很明显,flatMap()
正在进行一些内部后处理以实现该结果。 List('a','g')
如何与List('b','g')
结合?