为什么scala @tailrec无法在Option.flatMap上使用?

时间:2019-02-22 19:29:14

标签: scala monads tail-recursion

在scala中,以下两个功能的作用完全相同:

<!doctype html>
<html ng-app="myApp">

<body ng-controller="appController as vm">

  <p ng-if="$rootScope.timeoutFlag">Button Clicked</p>

  <button id="clickmeBtn">Click me!</button>

  <script src="node_modules/angular/angular.min.js"></script>
  <script src="scripts/app.controller.js"></script>
  <script src="scripts/app.service.js"></script>
</body>

</html>

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

  

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

为什么给出此错误?以及为什么这两个代码生成不同种类的JVM字节码

3 个答案:

答案 0 :(得分:4)

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

在某些情况下,编译器可以检测到调用fn是另一个函数中的最后一个动作,但通常情况下不是。而且这将依赖于其他功能的特定实现,因此如果更改了其他功能,tailrec批注可能会失效,这是不希望的依赖项。

答案 1 :(得分:2)

专门针对最后一个问题:

  

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

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

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

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

答案 2 :(得分:1)

请考虑以下内容:

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

我似乎很明显flatMap()正在进行一些内部后处理以达到该结果。 List('a','g')List('b','g')怎么结合?