Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/scala/16.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
为什么Scala库是用可变状态实现的?_Scala_Immutability - Fatal编程技术网

为什么Scala库是用可变状态实现的?

为什么Scala库是用可变状态实现的?,scala,immutability,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 } 它可以被实现为@tailr

为什么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
}

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

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

在您的示例中,无法从外部访问可变变量,因此不可能由于副作用而更改此可变变量

尽可能多地实施不变性总是好的,但是当性能很重要时,只要以安全的方式约束一些可变性就没有什么错


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

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

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

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

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

trait Iterator[A] { def next: A }
trait FunctionalIterator[A] { def next: (A, FunctionalIterator[A]) }
而第二个是笨拙和缓慢的,选择第一个是非常明智的

当一个功能上纯粹的实现对于大多数用例来说是优越的时,您通常会发现功能上纯粹的实现

当涉及到简单地使用
while
循环与递归时,任何一种都很容易维护,因此这实际上取决于编码者的偏好。请注意,
find
必须在
tailrec
案例中标记为
final
,因此
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 = {
                          ^
trait Foo{
def next:Int
def foo:Int={
var a=下一个
而(a<0)a=next
A.
}
}
定义特征Foo
特征条{
def next:Int
@tailrec def bar:Int={
val a=下一个
如果(a<0)巴,则为a
}
}
:10:错误:无法优化@tailrec注释的方法栏:
它既不是私有的,也不是最终的,因此可以被覆盖
@tailrec def bar:Int={
^

有一些方法可以解决这个问题(嵌套方法、final、重定向到私有方法等),但它倾向于添加样板文件,使
,而
在语法上更紧凑。

您的问题似乎假设如果这些方法是不可变的会更好。是这样吗?迭代器毕竟本质上是状态引擎。从我的(不广泛)来看根据经验,Scala和函数式语言通常更喜欢不变性。好吧,Linq在封面上是不变性的,但在封面下它使用了
yield
return
,其核心是一个可变状态引擎,很像这个引擎。在Haskell中,可变性本质上隐藏在monad magic后面。如果你深入研究,我怀疑你会发现到处都是易变性。尾部递归不会损害灵活性,你可以很容易地使用一个非final next方法,它调用另一个私有final方法,该方法具有tailrec注释
trait Foo{def bar(x:Int)=Foo(x);@tailrec final def Foo(x:Int):Int=Foo(x-1)}
@AloisCochard-好吧,当然,但是你正在添加更多的样板文件。(你可能也想将其私有化,这样你的接口就不会有两个方法做同样的事情。或者你可以将其作为一个内部函数。)有时,如果你能通过这样做来保持灵活性,那么更详细一点是很好的:--)…当然在这种特定情况下,
while
更简洁,但总是因为这个原因而宁愿
while
而不是尾部递归,这将是一种耻辱。@AloisCochard-它们的性能相当(在某些情况下,
while
更容易/快速地进行优化,但在某些情况下,
tailrec
的预热性能稍好)对于精通这两种编程风格的人来说,它们几乎同样容易理解。除了简洁性/可读性和教条之外,没有什么可供选择的了。考虑到这一选择,我每次都会选择简洁性/可读性。(这并不总是有利于
,但我会选择它所引导的任何方式。)我完全同意,这也是我倾向于使用的方法。