为什么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
的情况。 '方法?
答案 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
语法更紧凑的位置。