【问题标题】:Scala: Why is Recursion considered better in Scala than using Loops? (Just because of Mutability?) (Tail Recursion Vs Accumulators)Scala:为什么在 Scala 中递归比使用循环更好? (仅仅因为可变性?)(尾递归与累加器)
【发布时间】:2022-01-04 13:25:34
【问题描述】:

我知道,在使用 Loops 时,可变性会出现,并让事情难以追踪。但是在Scala中,递归被认为是循环的仅仅是因为可变性吗?

另外,我知道尾递归不会添加到您的调用堆栈中,但并非所有问题都可以使用尾递归解决,对吧?使用基于累加器的方法怎么样,它似乎也足以避免堆栈溢出情况?尾递归和基于累加器的递归方法之间有什么更好的性能?

简而言之,我有两个问题:

  1. 由于尾递归不能用于解决所有循环问题(或者可以吗?)循环不是更好还是某些特定用例的唯一解决方案?
  2. 尾递归和基于累加器的递归有什么区别?根据空间复杂度和调用堆栈占用情况,哪一种性能更高?

编辑 1:

在基于累加器的递归(因子函数)中转换的非尾递归函数(使用中间尾递归函数)示例:

没有累加器:

def factorial(n: Int): Int = {
  if (n <= 1) 1
  else n * factorial(n - 1)
}

带累加器:

def tailRecFactorial(n: Int): BigInt = {
    @tailrec
    def factorialHelper(x: Int, accumulator: BigInt): BigInt = {
      if (x <= 1) accumulator
      else factorialHelper(x - 1, x * accumulator)
    }
    factorialHelper(n, 1)
  }

【问题讨论】:

标签: scala recursion


【解决方案1】:

Scala 是一种函数式语言,通常认为函数式代码优于非函数式代码。对while 的反对意见是它依赖于在迭代期间更改的值,因此无法正常工作。

回答具体问题:

  1. 任何循环都可以表示为一个纯尾递归函数,但要确定将什么状态传递给递归调用可能会非常麻烦。

  2. “尾递归”和“基于累加器的递归”并不相互排斥。尾递归函数是在至少一个代码路径上调用自身作为最后一个操作的任何函数。基于累加器的递归只是意味着将部分结果传递给递归调用,作为将非尾递归函数转换为尾递归函数的一种方式。

【讨论】:

    【解决方案2】:

    嗯,首先,不确定你所说的基于累加器的递归是什么意思?

    第二,Scala只有while循环,所有while循环都可以用(tail)递归的形式来表示;哈哈。

    第三,是的,我们更喜欢递归而不是 while 以避免可变性。
    但是,递归本身的级别太低了。一般来说,你应该更喜欢像mapfoldLeft这样的高阶组合器

    第四,虽然可变性包含在单个方法中是可以的,但通常最好在任何地方保持一致并保持一切不可变(除非你有很好的理由)不变性只是因为。

    【讨论】:

    • 我非常不同意递归是“太低级”。递归是每个函数式程序员都应该在他们的工具箱中拥有并在库方法不够灵活时使用的一种技术。我在这里看到了很多复杂且不透明的fold 算法的答案,其中递归函数更简单、更清晰。
    • @Tim 当然,完全同意这是一种每个人都应该知道的技术,并且与所有事情一样,有时是最好的解决方案;这就是为什么我说“你应该更喜欢”而不是“你必须总是使用”。尽管如此,这并不能否认递归比高阶组合器级别低的事实。 - 另外,你知道我实际上非常喜欢尾递归,而且我在这里的许多答案都使用尾递归,BTW 被批评使用尾递归而不是foldLeft
    • 是的,这句话不是针对你的 :) 我想我的想法是递归是一种抽象技术,适用于广泛的问题,而高阶组合更集中且范围有限.但也许高级与低级并不是比较两者的有用方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-17
    • 2018-03-08
    • 2013-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多