【问题标题】:Why can't Option.fold be used tail recursively in Scala?为什么不能在Scala中递归使用Option.fold?
【发布时间】:2022-01-24 06:23:25
【问题描述】:

下面,sumAllIf 是尾递归的,sumAllFold 不是。但是,sumAllIf 实际上具有相同的实现。这是 Scala 编译器(或 Scala 库)的缺点,还是我忽略了什么?

def maybeNext(in: Int): Option[Int] = if in < 10 then Some(in + 1) else None

// The Scala library implements Option.fold like this:
// @inline final def fold[B](ifEmpty: => B)(f: A => B): B =
//   if (isEmpty) ifEmpty else f(this.get)
@annotation.tailrec
def sumAllIf(current: Int, until: Int, sum: Int): Int =
  val nextOption = maybeNext(current)
  if (nextOption.isEmpty) sum else sumAllIf(nextOption.get, until, sum + nextOption.get)

// However, with Scala 3.1.0 and earlier, this is NOT tail recursive:
def sumAllFold(current: Int, until: Int, sum: Int): Int =
  maybeNext(current).fold(sum)(next => sumAllFold(next, until, sum + next))

@main def main(): Unit =
  println(sumAllIf(0, 10, 0))
  println(sumAllFold(0, 10, 0))

这个问题类似于问题Scala @tailrec with fold,但在这里我想了解一下为什么以及这是否可以将来会得到支持。

该示例适用于 Scala 3.1,但问题本身也适用于 Scala 2.x。

  • 从您附加的链接中的答案; \"... Scala 编译器无法计算出折叠的结果\" 因为编译器无法计算出折叠的结果,因此无法使其尾递归。
  • 这来自尾递归的定义。该调用不在尾部位置,并且如果没有内联,编译器对于如何优化它并不明显。如果您真的需要尾递归,请自己编写递归。
  • 这回答了你的问题了吗? Scala @tailrec with fold

标签: scala tail-recursion fold


【解决方案1】:

递归调用发生在 lambda 内部。所以它不是尾递归调用,除非编译器将折叠和 lambda 内联到您自己的方法中,然后才测试它是否是尾递归。但是编译器不会自动执行此操作,并且可能永远不会自动执行此操作。

好消息是,在 Scala 3 中,您可以很容易地解决这个问题,而且从理论上讲,标准库可能会被调整以利用这一点。只需将fold 显式实现为带有内联参数的内联方法。

inline def fold[A, B](opt: Option[A])(inline onEmpty: B)(inline f: A => B): B =
  opt match
    case Some(a) => f(a)
    case None => onEmpty

@annotation.tailrec
def sumAllFold(current: Int, until: Int, sum: Int): Int =
  fold(maybeNext(current))(sum)(next => sumAllFold(next, until, sum + next))

请注意,内联参数自动具有按名称语义,因此onEmpty 已经是按名称,无需将类型更改为=&gt; B

【讨论】:

【解决方案2】:

下面,sumAllIf 是尾递归,sumAllFold 不是。但是,sumAllIf 实际上具有相同的实现。这是 Scala 编译器(或 Scala 库)的缺点,还是我忽略了什么?

这只是简单的定义尾递归.一个尾声是子程序中的最后一次调用。递归是子程序调用自身的时候。尾递归是当尾部调用是递归调用时,或者当递归调用处于尾部位置时 - 换句话说,它是当子例程将自身作为最后一次调用时。

在你的情况下,最后一个电话是fold,而不是sumAllFold

这不是 Scala 编译器或 Scala 库的缺点。 sumAllFold 不是尾递归,因为它的尾调用不是递归调用,递归调用不是尾调用。换句话说,它不是尾递归,因为它根本不是尾递归。

这与问你的蓝色车不是黄色是否是你的机械师的缺点基本相同。它不是。你的蓝色车不是黄色的,因为它根本不是。

【讨论】:

    猜你喜欢
    • 2014-01-18
    • 1970-01-01
    • 1970-01-01
    • 2012-02-25
    • 2013-09-14
    • 2014-05-04
    • 2022-01-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多