为什么Scala维护集合类型不返回Iterable(如.Net)?

时间:2011-05-31 08:26:30

标签: scala collections

在Scala中,你可以做到

val l = List(1, 2, 3)
l.filter(_ > 2)               // returns a List[Int]
val s = Set("hello", "world")
s.map(_.length)               // returns a Set[Int]

问题是:为什么这有用?

Scala集合可能是唯一执行此操作的现有集合框架。 Scala社区似乎同意需要此功能。然而,似乎没有人会错过其他语言的这种功能。示例C#(与Scala匹配的修改命名):

var l = new List<int> { 1, 2, 3 }
l.filter(i => i > 2)          // always returns Iterable[Int]
l.filter(i => i > 2).toList   // if I want a List, no problem
l.filter(i => i > 2).toSet    // or I want a Set

在.NET中,我总是回到Iterable,这取决于我想用它做什么。 (这也使.NET集合非常简单。)

使用Set的Scala示例强制我从一组字符串中创建一组长度。但是,如果我只想迭代长度,或构建长度列表,或保持Iterable以后过滤它,该怎么办?立即构建一个Set似乎毫无意义。 EDIT :collection.view提供更简单的.NET功能,很好)

我相信你会向我展示.NET方法绝对错误或杀死性能的例子,但我看不到任何问题(使用.NET多年)。

4 个答案:

答案 0 :(得分:10)

对您的问题不是一个完整的答案,但Scala从不强制您使用一种集合类型而不是另一种集合类型。你可以自由编写这样的代码:

import collection._
import immutable._

val s = Set("hello", "world")
val l: Vector[Int] = s.map(_.length)(breakOut)

详细了解Daniel Sobral's detailed answerbreakOut与其他问题的关联。

如果您希望懒惰地评估mapfilter,请使用此字段:

s.view.map(_.length)

这整个行为使您可以轻松集成新的集合类并继承标准集合的所有强大功能而无需重复代码,所有这些都可确保YourSpecialCollection#filter返回YourSpecialCollection的实例;如果YourSpecialCollection#map支持要映射到的类型,则YourSpecialCollection返回map的实例,如果不支持,则返回内置的回退集合(就像你在{1}上调用BitSet时所发生的那样.toMySpecialCollection)。当然,C#迭代器没有{{1}}方法。

另请参阅:The Architecture of Scala Collections中的“集成新集和地图”。

答案 1 :(得分:9)

Scala遵循“统一返回类型原则”,确保您始终以适当的返回类型结束,而不是像C#中那样丢失该信息。

C#这样做的原因是它们的类型系统不足以提供这些保证而不会覆盖每个子类中每个方法的整个实现。 Scala通过使用更高的类型来解决这个问题。

为什么Scala有唯一的集合框架呢?因为它比大多数人认为的更难,特别是当字符串和数组之类的东西不是“真正的”集合时也应该集成:

// This stays a String:
scala> "Foobar".map(identity)
res27: String = Foobar
// But this falls back to the "nearest" appropriate type:
scala> "Foobar".map(_.toInt)
res29: scala.collection.immutable.IndexedSeq[Int] = Vector(70, 111, 111, 98, 97, 114)

答案 2 :(得分:7)

如果你有一个Set,并且对它的操作返回Iterable,而它的运行时类型仍然是Set,那么你将丢失有关其行为的重要信息,并且访问特定于集合的方法。

BTW: 其他语言表现相似,比如Haskell,它对Scala产生了很大的影响。 Haskell版本的map看起来像翻译成Scala(没有implicit魔法):

//the functor type class
trait Functor[C[_]] {
   def fmap[A,B](f: A => B, coll: C[A]) : C[B]
}

//an instance  
object ListFunctor extends Functor[List] {
   def fmap[A,B](f: A => B, list: List[A]) : List[B] = list.map(f)
}

//usage 
val list = ListFunctor.fmap((x:Int) => x*x, List(1,2,3))

我认为Haskell社区也重视此功能: - )

答案 3 :(得分:4)

这是一致的问题。事情就是这样,并回归像他们这样的东西。你可以依赖它。

你在这里所做的差异就是严格。立即评估严格方法,而仅根据需要评估非严格方法。这会产生后果。举个简单的例子:

def print5(it: Iterable[Int]) = {
    var flag = true
    it.filter(_ => flag).foreach { i =>
        flag = i < 5
        println(i)
    }
}

使用这两个集合进行测试:

print5(List.range(1, 10))
print5(Stream.range(1, 10))

这里,List是严格的,所以它的方法是严格的。相反,Stream是非严格的,因此它的方法是非严格的。

所以这与Iterable根本没有关系 - 毕竟,ListStream 都是 Iterable。更改集合返回类型可能会导致所有类型的问题 - 至少,它会使维持持久数据结构的任务变得更难。

另一方面,即使在严格的收集上,延迟某些操作也是有利的。以下是一些方法:

// Get an iterator explicitly, if it's going to be used only once
def print5I(it: Iterable[Int]) = {
    var flag = true
    it.iterator.filter(_ => flag).foreach { i =>
        flag = i < 5
        println(i)
    }
}

// Get a Stream explicitly, if the result will be reused
def print5S(it: Iterable[Int]) = {
    var flag = true
    it.toStream.filter(_ => flag).foreach { i =>
        flag = i < 5
        println(i)
    }
}

// Use a view, which provides non-strictness for some methods
def print5V(it: Iterable[Int]) = {
    var flag = true
    it.view.filter(_ => flag).foreach { i =>
        flag = i < 5
        println(i)
    }
}

// Use withFilter, which is explicitly designed to be used as a non-strict filter
def print5W(it: Iterable[Int]) = {
    var flag = true
    it.withFilter(_ => flag).foreach { i =>
        flag = i < 5
        println(i)
    }
}