为什么不能在option.flatmap上使用scala@tailrec?

t9aqgxwy  于 2021-07-14  发布在  Java
关注(0)|答案(3)|浏览(395)

在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字节码

pzfprimi

pzfprimi1#

为了 fn 要实现尾部递归,递归调用必须是函数中的最后一个操作。如果你通过了 fn 另一个函数,如 flatMap 调用后,另一个函数可以自由执行其他操作 fn 因此编译器不能确定它是尾部递归的。
在某些情况下,编译器可以检测到该调用 fn 是其他函数中的最后一个操作,但不是在一般情况下。这将依赖于另一个功能的具体实现 tailrec 如果另一个函数被更改,注解可能会变得无效,这是一种不需要的依赖关系。

fzsnzjdm

fzsnzjdm2#

特别是最后一个问题:
为什么这两个代码会生成不同类型的jvm字节码
因为在jvm上,不能保证jar包含 Option 运行时的类与编译时的类相同。这是好的,因为否则即使是库的次要版本(包括标准java和scala库)也会不兼容,并且您需要所有依赖项都使用它们的公共依赖项的相同次要版本。
如果那个班没有合适的 flatMap 方法,你会得到 AbstractMethodError 但scala的语义要求 flatMap 方法必须被调用。因此编译器必须发出字节码才能真正调用该方法。
kotlin通过使用 inline 函数和Scala3也会支持它们,但我不知道它是否会在这种情况下使用它们。

5f0d552i

5f0d552i3#

考虑以下几点:

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

我很明显 flatMap() 正在进行一些内部后处理以实现该结果。不然怎么办 List('a','g') 与…结合 List('b','g') ?

相关问题