斯卡拉,返回types指南 – 当更喜欢seq,迭代,可遍历

你什么时候select键入一个给定函数的返回types为Seq vs Iterable vs Traversable (或者Seq的层次结构中的更深层次)?

你如何做出这个决定? 我们有很多代码默认返回Seq (通常从DB查询和连续变换的结果开始)。 我倾向于在默认情况下使返回types为Traversable ,并且在特定期望给定的顺序时使用Seq 。 但是我没有强有力的理由。

我完全熟悉每个特质的定义,所以请不要回答定义的条款。

这是一个很好的问题。 你必须平衡两个问题:

  • (1)尽量保持你的API一般,所以你可以稍后改变实现
  • (2)给调用者一些有用的操作来执行收集

在哪里(1)要求你对这个types(例如Seq Iterable )做一个小的具体的描述,(2)要求你相反。

即使返回types只是Iterable ,也可以让我们说一个Vector ,所以如果调用者希望获得额外的权力,它可以只调用.toSeq.toIndexedSeq ,并且该操作对于一个Vector是便宜的。

作为平衡的衡量标准,我想补充一点:

  • (3)使用一种反映数据如何组织的types。 例如,当你可以假设数据确实有序列时,给出Seq 。 如果你可以假定没有两个相等的对象可以发生,给一个Set 。 等等。

这是我的经验法则:

  • 尝试只使用一小部分集合: SetMapSeqIndexedSeq
  • 我经常违反这个规则,但是使用List来支持Seq 。 它允许调用者与抽取器进行模式匹配
  • 仅使用不可变types(例如, collection.immutable.Setcollection.immutable.IndexedSeq
  • 不要使用具体的实现( Vector ),而是使用相同API的一般types( IndexedSeq
  • 如果你封装一个可变结构,只返回Iterator实例,调用者可以很容易地生成一个严格的结构,例如通过调用toList
  • 如果你的API很小,并明确地调整为“大数据吞吐量”,使用IndexedSeq

当然,这是我个人的select,但我希望听起来很理智。

使您的方法的返回types尽可能具体。 那么如果调用者想保持它作为一个SuperSpecializedHashMap或者把它作为一个GenTraversableOnce ,他们可以。 这就是为什么编译器默认推断出最具体的types。

  • 默认情况下使用Seq
  • 当需要按索引访问时使用IndexedSeq
  • 只有在特殊情况下才能使用

这些是“常识”指导原则。 它们简单实用,在平衡原则和绩效的同时,在实践中运作良好。 原则是:

  1. 使用反映数据如何组织的types(感谢OP和Ziggystar)。
  2. 在方法参数和返回types中使用接口types。 API的input和返回types受益于通用性的灵活性。

Seq满足两个原则。 如http://docs.scala-lang.org/overviews/collections/seqs.html中所述 :

一个序列是一种可迭代的,具有[有限]长度,其元素具有固定的索引位置,从0开始。

90%的时间,你的数据是一个Seq。

其他说明:

  • List是一个实现types,所以你不应该在API中使用它。 Vector例如不经过转换就不能用作List
  • Iterable不定义length 。 跨越有限序列和潜在的无限stream的可迭代的抽象。 大多数时候人们都在处理有限的序列,所以你“有一个长度”, Seq反映了这一点。 通常你不会使用长度。 但是它经常需要,而且很容易提供,所以使用Seq

缺点:

这些“常识”惯例有一些轻微的缺点。

  • 你不能使用List cons模式匹配,即case head :: tail => ...可以使用:++:如此处所述。 然而重要的是,在Nil匹配仍然按照Scala描述的那样工作:模式匹配Seq [Nothing] 。

脚注:

  • 我在这里不讨论Map ,因为这个问题显然没有问这个问题。
  • 我只是在这里解决不可变的集合。
  • 我build议的指导方针与我应该使用List [A]还是Seq [A]还是其他?

我所遵循的一个经验法则是,根据实施情况,尽可能使返回types尽可能具体,参数types尽可能通用。 这是一个易于遵循的规则,它为您提供一致的保证types属性与最大的自由度。

比方说,如果你有一个函数实现,它只是通过像mapfilterfold这样的方法遍历数据结构 – 那些在Traversable特性中实现的实现,你可以期望它在任何types的input集合上都能够执行 – 无论是ListVectorHashSet甚至HashMap ,所以你的input参数应该被指定为Traversable[T] 。 函数的输出types的select应该仅取决于其实现:在这种情况下,它也应该是可Traversable 。 如果在你的函数中强制使用toListtoSeqtoSet等方法把这个数据结构强制为一些更具体的types,你应该指定适当的types。 注意实现和返回types之间的一致性?

如果你的函数通过索引来访问input的元素,input应该被指定为IndexedSeq ,因为它是最通用的类​​型,为方法apply有效实现提供了保证。

在抽象成员的情况下,同样的规则也适用,唯一的区别就是你应该根据你计划如何使用它们而不是实现来指定返回types,因此大多数情况下它们会比实现更普遍。 SeqSetMap的分类select是最值得期待的。

遵循这个规则,你可以保护自己免受常见瓶颈情况的困扰,例如,当项目被附加到ListcontainsSeq而不是Set上调用时,你的程序仍然是一个很好的自由度,并且在select意义上是一致的types。