为什么Scala库以可变状态实现?

时间:2013-04-22 19:33:51

标签: scala immutability

为什么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方法实现,可能类似

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的情况。 '方法?

3 个答案:

答案 0 :(得分:4)

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

在你的例子中,无法从外部访问mutable var,因此这个可变变量不可能因副作用而改变。

尽可能强制执行不变性总是好的,但是当性能很重要时,只要它以安全的方式受到限制,就会出现一些可变性。

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

答案 1 :(得分:3)

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

但是,迭代器无论如何都是一个可变的抽象,因此使用尾递归方法来避免var这个短代码片段本地的问题是值得怀疑的。

答案 2 :(得分:0)

Scala不是为功能纯度设计的,而是为广泛使用的功能而设计的。部分原因包括尝试最有效的基本库例程实现(当然不是普遍适用,但通常是这样)。

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

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

第二个是尴尬和慢,选择第一个是非常明智的。

如果功能纯粹的实现对于大多数用例来说都是优越的,那么您通常会找到功能纯粹的实现。

当谈到简单地使用while循环与递归时,任何一个都很容易维护,所以它真的取决于编码器的偏好。请注意,find必须在final案例中标记tailrec,因此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 = {
                          ^

有很多方法可以解决这个问题(嵌套方法,最终方法,重定向到私有方法等),但它往往会将样板添加到while语法更紧凑的位置。