55

您何时选择将给定函数的返回类型键入为Seqvs Iterablevs Traversable(或者甚至在Seq的层次结构中更深)?

你如何做出这个决定?我们有很多Seq默认返回 s 的代码(通常从数据库查询和连续转换的结果开始)。我倾向于在Traversable默认情况下以及Seq在特别期望给定订单时创建返回类型。但我没有这样做的充分理由。

我非常熟悉每个特征的定义,所以请不要用定义术语来回答。

4

5 回答 5

43

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

  • (1) 尽量保持你的 API 通用,以便以后可以更改实现
  • (2) 给调用者一些有用的操作来对集合执行

其中 (1) 要求您尽可能少地具体说明类型(例如Iterableover Seq),而 (2) 要求您相反。

即使返回类型是 just Iterable,你仍然可以返回让我们说 a Vector,所以如果调用者希望获得额外的权力,它可以只调用.toSeqor .toIndexedSeq,并且该操作对于 a 来说很便宜Vector

作为衡量平衡的标准,我要补充第三点:

  • (3) 使用一种能反映数据组织方式的类型。例如,当您可以假设数据确实有序列时,请给出Seq. 如果您可以假设不可能出现两个相等的对象,请给出Set. 等等。

以下是我的经验法则:

  • 尝试只使用一小组集合:Set, Map, Seq,IndexedSeq
  • 不过,我经常违反以前的规则,使用List支持Seq. 它允许调用者使用 cons 提取器进行模式匹配
  • 仅使用不可变类型(例如collection.immutable.Setcollection.immutable.IndexedSeq
  • 不要使用具体实现 ( Vector),而是使用通用类型 (IndexedSeq ),而是使用提供相同 API
  • 如果您正在封装一个可变结构,只返回Iterator实例,那么调用者可以轻松地生成一个严格的结构,例如通过调用toList
  • 如果您的 API 很小并且明显针对“大数据吞吐量”进行了调整,请使用IndexedSeq

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

于 2012-07-28T17:09:57.787 回答
9
  • 默认情况下在任何地方使用Seq
  • IndexedSeq当您需要按索引访问时使用。
  • 仅在特殊情况下使用其他任何东西。

这些是“常识”准则。它们简单、实用,并且在实践中运行良好,同时平衡了原则和性能。原则是:

  1. 使用反映数据组织方式的类型(感谢 OP 和 ziggystar)。
  2. 在方法参数和返回类型中都使用接口类型。API 的输入和返回类型都受益于通用性的灵活性。

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

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

90% 的情况下,您的数据是 Seq。

其他注意事项:

  • List是一种实现类型,因此您不应在 API 中使用它。例如,如果不经过转换,就不能将AVector用作 a 。List
  • Iterable没有定义lengthIterable跨有限序列和潜在无限流的抽象。大多数时候,人们都在处理有限序列,所以你“有一个长度”,并Seq反映了这一点。通常,您实际上不会使用长度。但它经常需要,而且很容易提供,所以使用Seq.

缺点:

这些“常识”约定有一些轻微的缺点。

  • 您不能使用 List cons 模式匹配,即case head :: tail => .... 您可以使用:+和,如此+:所述。然而,重要的是,匹配仍然可以按照Scala:模式匹配 Seq[Nothing]中的描述进行。Nil

脚注:

于 2015-05-01T21:38:15.847 回答
5

使您的方法的返回类型尽可能具体。然后,如果调用者想要将其保留为 aSuperSpecializedHashMap或将其键入为 a GenTraversableOnce,他们可以。这就是编译器默认推断出最具体的类型的原因。

于 2012-07-28T16:50:05.800 回答
1

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

map比如说,如果你有一个函数实现,它只是用,filterfold- 那些在 trait 中实现的方法来遍历数据结构Traversable,你可以期望它在任何类型的输入集合上同样执行 - 无论是 a ListVectorHashSet甚至是 a HashMap,因此您的输入参数应指定为Traversable[T]。函数输出类型的选择应该只取决于它的实现:在这种情况下也应该Traversable如此。但是,如果在您的函数中使用toList,toSeq或之类的方法将此数据结构强制为某些更具体的类型toSet,则应指定适当的类型。注意到实现和返回类型之间的一致性了吗?

如果您的函数通过索引访问 input 的元素,则应将 input 指定为IndexedSeq,因为它是最通用的类​​型,可为您提供有效实现 method 的保证apply

在抽象成员的情况下,相同的规则适用,唯一的区别是您应该根据您计划如何使用它们而不是实现来指定返回类型,因此它们通常比实现更通用。分类选择SeqSetMap是最值得期待的。

遵循这条规则,您可以保护自己免受非常常见的瓶颈情况的影响,例如,当项目被附加到List或被contains调用 aSeq而不是 aSet时,您的程序仍然具有很好的自由度,并且在类型选择方面是一致的。

于 2012-07-30T11:09:06.070 回答
0

快速说明:在 Scala 2.13.x 中,Traversable是不可能的。 Iterable更普遍,并且确定向前发展二元性是不合理的。 Iterable现在位于集合层次结构的顶部并且Traversable已被弃用。

于 2020-11-18T02:00:02.447 回答