在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:它包含一个递归调用,不在尾部位置选项(str).filter(\u0.nonempty).flatmap{v=>
为什么会出现这个错误?为什么这两个代码会生成不同类型的jvm字节码
3条答案
按热度按时间pzfprimi1#
为了
fn
要实现尾部递归,递归调用必须是函数中的最后一个操作。如果你通过了fn
另一个函数,如flatMap
调用后,另一个函数可以自由执行其他操作fn
因此编译器不能确定它是尾部递归的。在某些情况下,编译器可以检测到该调用
fn
是其他函数中的最后一个操作,但不是在一般情况下。这将依赖于另一个功能的具体实现tailrec
如果另一个函数被更改,注解可能会变得无效,这是一种不需要的依赖关系。fzsnzjdm2#
特别是最后一个问题:
为什么这两个代码会生成不同类型的jvm字节码
因为在jvm上,不能保证jar包含
Option
运行时的类与编译时的类相同。这是好的,因为否则即使是库的次要版本(包括标准java和scala库)也会不兼容,并且您需要所有依赖项都使用它们的公共依赖项的相同次要版本。如果那个班没有合适的
flatMap
方法,你会得到AbstractMethodError
但scala的语义要求flatMap
方法必须被调用。因此编译器必须发出字节码才能真正调用该方法。kotlin通过使用
inline
函数和Scala3也会支持它们,但我不知道它是否会在这种情况下使用它们。5f0d552i3#
考虑以下几点:
我很明显
flatMap()
正在进行一些内部后处理以实现该结果。不然怎么办List('a','g')
与…结合List('b','g')
?