在Scala 2.8集合中,为什么在Iterable之上添加了Traversable类型?

20

我知道要使一个类成为Traversable,只需要拥有一个foreach方法。而实现Iterable则需要一个iterator方法。

Scala 2.8集合SID和“Fighting Bitrot with Types”论文基本上对Traversable被添加的原因保持沉默。SID只是说:“David McIver…提出了Traversable作为Iterable的一般化。”

我从IRC上的讨论中隐约得知,这似乎与在遍历集合时回收资源有关?

下面的内容可能与我的问题相关。例如,在TraversableLike.scala中存在一些看起来很奇怪的函数定义:

def isEmpty: Boolean = {
  var result = true
  breakable {
    for (x <- this) {
      result = false
      break
    }
  }
  result
}

我认为肯定有一个很好的理由,不能只简单地写成:

def isEmpty: Boolean = {
  for (x <- this)
    return false
  true
}

Traversable 在《Scala编程》第二版的第24章中得到了解释;在某些情况下,实现 foreach 比实现 iterator 更容易高效。 - Blaisorblade
关于上一个问题:https://github.com/scala/scala/pull/5101 - floating cat
3个回答

11

我在IRC上向David McIver询问了此事。他表示他已不记得原因,但其中包括:

  • "实现迭代器时通常很麻烦......"

  • 迭代器有时是"不安全的(由于循环开始和结束时的设置/拆卸)"

  • 通过foreach而不是通过迭代器实现某些操作所希望获得的效率提升(当前HotSpot编译器未必已经证明获得这些提升)


2
请注意,在Scala 2.13集合重写中,我们再次摒弃了Traversable并回到了基于迭代器的设计。 - Seth Tisue

4

我猜测一个原因是,使用具有抽象foreach方法的集合编写具体实现比使用具有抽象iterator方法的集合更容易。例如,在C#中,您可以将IEnumerable<T>GetEnumerator方法的实现编写为foreach方法:

IEnumerator<T> GetEnumerator() 
{
    yield return t1;
    yield return t2;
    yield return t3;
}

编译器会生成一个适当的状态机来驱动对 IEnumerator 的迭代。在Scala中,您需要编写自己的 Iterator[T] 实现来完成此操作。对于 Traversable,您可以执行与上述实现等效的操作:

def foreach[U](f: A => U): Unit = {
  f(t1); f(t2); f(t3)
}

我对Scala还比较新,但既然你实际上没有返回任何东西,为什么不只写成def foreach[U](f: A => U){f(t1); f(t2); f(t3) }呢? - tstenner
foreach 声明返回 Unit,因此会丢弃类型为 U 的主体块 f(t3) 的结果。 - Peter Schmitz

-3

关于你最后一个问题:

def isEmpty: Boolean = {
  for (x <- this)
    return false
  true
}

这将被编译器粗略地翻译为:

def isEmpty: Boolean = {
  this.foreach(x => return false)
  true
}

所以你根本无法跳出foreach循环,isEmpty始终会返回true。

这就是为什么构建了“hacky”Breakable,它通过抛出控制异常来跳出foreach循环,在breakable中捕获并返回。


3
实际上,你对于 Scala 的 return 是错误的。 Scala 支持非局部返回。 - Eastsun
哦,这真让人惊讶,你说得对。这一直有效吗?? - hotzen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接