2

为什么 Scala 标准库中的某些方法使用可变状态实现?

例如,find作为类的一部分的方法scala.Iterator被实现为

def find(p: A => Boolean): Option[A] = {
  var res: Option[A] = None
  while (res.isEmpty && hasNext) {
    val e = next()
    if (p(e)) res = Some(e)
  }
  res
}

可以作为@tailrec'd 方法实现,可能类似于

def findNew(p: A => Boolean): Option[A] = {

  @tailrec
  def findRec(e: A): Option[A] = {
    if (p(e)) Some(e)
    else {
      if (hasNext) findRec(next())
      else None
    }
  }

  if (hasNext) findRec(next())
  else None
}

现在我想一个论点可能是可变状态的使用,而while循环可能更有效,这在库代码中非常重要,可以理解,但对于@tailrec'd 方法来说真的是这样吗?

4

3 回答 3

4

只要他不被共享,拥有可变状态就没有坏处。

在您的示例中,无法从外部访问可变 var,因此该可变变量不可能由于副作用而发生变化。

尽可能地强制执行不变性总是好的,但是当性能很重要时,只要以安全的方式限制它,拥有一些可变性就没有错。

注意:迭代器是一种没有副作用的数据结构,这可能会导致一些奇怪的行为,但这是另一回事,绝不是以这种方式设计方法的原因。您也会在不可变数据结构中找到类似的方法。

于 2013-04-22T19:39:15.637 回答
3

在这种情况下,很可能与循环tailrec具有相同的性能。while我想说,在这种情况下,while循环解决方案更短更简洁。

但是,迭代器无论如何都是一个可变的抽象,因此使用尾递归方法来避免这种情况的好处var是值得怀疑的。

于 2013-04-22T19:40:21.967 回答
0

Scala 的设计目的不是为了功能纯度,而是为了广泛有用的功能。其中一部分包括尝试最有效地实现基本库例程(当然不是普遍正确,但经常如此)。

因此,如果您有两个可能的接口:

trait Iterator[A] { def next: A }
trait FunctionalIterator[A] { def next: (A, FunctionalIterator[A]) }

而第二个又笨又慢,选择第一个还是比较明智​​的。

当一个纯功能的实现对于大部分用例来说都优越时,你通常会找到一个纯功能的实现。

当涉及到简单地使用while循环与递归时,任何一个都足够容易维护,因此这实际上取决于编码器的偏好。请注意,find必须finaltailrec案例中进行标记,以便while保留更多灵活性:

trait Foo {
  def next: Int
  def foo: Int = {
    var a = next
    while (a < 0) a = next
    a
  }
}

defined trait Foo


trait Bar {
  def next: Int
  @tailrec def bar: Int = {
    val a = next
    if (a < 0) bar else a
  }
}

<console>:10: error: could not optimize @tailrec annotated method bar:
it is neither private nor final so can be overridden
             @tailrec def bar: Int = {
                          ^

有一些方法可以解决这个问题(嵌套方法、final、重定向到私有方法等),但它往往会在while语法上更紧凑的地方添加样板。

于 2013-04-22T20:09:58.307 回答