是否应该将Scala不可变的case类定义为包含Seq [T],immutable.Seq [T],List [T]或Vector [T]?

时间:2013-03-29 21:23:22

标签: scala collections scala-collections

如果我们想要定义一个包含单个对象的case类,比如一个元组,我们可以很容易地做到:

sealed case class A(x: (Int, Int))

在这种情况下,检索“x”值将花费很少的时间,并且该类只占用一小段恒定的空间,无论它是如何创建的。

现在,我们假设我们想要保留一系列值;我们可以这样:

sealed final case class A(x: Seq[Int])

这似乎和以前一样,除了现在存储和读取所有x的时间与x.length成正比。

然而,事实并非如此,因为有人可以这样做:

val hugeList = (1 to 1000000000).toList
val a = A(hugeList.view.filter(_ == 500000000))

在这种情况下,一个对象看起来像一个无辜的case类,在一个序列中持有一个int,但实际上它需要几GB的内存,并且每次访问该单个元素将花费大约几秒的时间。

这可以通过指定List [T]之类的东西而不是Seq [T]来修复;然而,这似乎很难看,因为它增加了对特定实现的引用,而实际上其他表现良好的实现,如Vector [T],也会这样做。

另一个令人担忧的问题是,人们可以传递一个可变的Seq [T],所以似乎至少应该使用immutable.Seq而不是scala.collection.Seq(尽管编译器实际上不能强制执行不变性力矩)。

查看大多数库,似乎常见的模式是使用scala.collection.Seq [T],但这真的是个好主意吗?

或者也许正在使用Seq只是因为它是最短的类型,事实上最好使用immutable.Seq [T],List [T],Vector [T]或其他东西?

编辑

中添加了新文字

查看类库,scala.reflect.api.Trees等一些最核心的功能确实使用了List [T],一般来说使用具体的类似乎是个好主意。

但是,为什么使用List而不是Vector?

Vector有O(1)/ O(log(n))长度,前置,追加和随机访问,渐近变小(由于vtable和下一个指针,List大约3-4倍),并支持缓存高效和并行计算,而List没有那些属性,除了O(1)prepend。

所以,我个人倾向于将Vector [T]作为库数据结构中暴露的东西的正确选择,其中人们不知道库用户需要什么操作,尽管它似乎不太受欢迎

1 个答案:

答案 0 :(得分:1)

首先,您要谈论空间和时间要求。就空间而言,您的对象将始终与集合一样大。无论你是否包装一个可变或不可变的集合,该集合出于显而易见的原因需要在内存中,并且包装它的case类不需要任何额外的空间(除了它自己的小对象引用)。因此,如果你的收藏品占用“千兆字节的内存”,这是你的收藏问题,而不是你是否将它包装在一个案例类中。

然后,您继续争辩说使用视图而不是急切的集合时会出现问题。但问题是问题实际上是什么?您可以使用延迟过滤集合的示例。通常,运行过滤器将是O(n)操作,就像您在原始列表上进行迭代一样。在该示例中,如果该集合是严格的,则对于连续调用将是O(1)。但这是您的案例类的调用站点的问题,而不是您的案例类的定义。

我看到的唯一有效点是关于可变集合。给定案例类的定义语义,您实际上应该只使用有效不可变对象作为参数,因此要么是纯不可变集合,要么没有实例具有更多写访问权限的集合。

Scala中存在一个设计错误,scala.Seq没有别名collection.immutable.Seq,而是一般seq,可以是可变的,也可以是不可变的。我建议不要使用不合格的Seq。这确实是错误的,应该在Scala标准库中进行纠正。请改用 collection.immutable.Seq ,或者如果不需要订购该集合, collection.immutable.Traversable

所以我同意你的怀疑:

  

查看大多数库,似乎常见的模式是使用scala.collection.Seq [T],但这真的是个好主意吗?

没有!不好。它可能很方便,因为您可以传递Array例如没有显式转换,但我认为更清洁的设计需要不变性。