最佳实践:“如果不是不可变的创建副本” - 模式

时间:2013-01-13 09:15:01

标签: scala immutability scala-collections

我有一个函数,它以Seq[_]作为参数,并以Seq作为val成员返回一个不可变的类实例。如果Seq是可变的,我显然想要创建一个防御性副本,以保证我的返回类实例不能被修改。

此模式的最佳做法是什么?首先,我感到惊讶的是, 可能会使函数重载

  def fnc(arg: immutable.Seq[_]) = ...
  def fnc(arg: mutable.Seq[_]) = ...

我也可以模式匹配:

  def fnc(arg: Seq[_]) = arg match {
    case s: immutable.Seq[_] => { println("immutable"); s}
    case s: mutable.Seq[_] => {println("mutable"); List()++s }
    case _: ?
  }   

但我不确定_案例。保证argimmutable.Seq还是mutable.Seq?我也不知道List()++s是否是转换它的正确方法。我在SO上看过很多帖子,但其中大多数都是2.8或更早的帖子。

Scala-Collections是否“足够智能”,我可以随时(没有模式匹配)写List()++s并且如果不可变则获得相同的实例,如果可变则获得深拷贝?

建议的方法是什么?

3 个答案:

答案 0 :(得分:4)

如果你想同时支持,你将需要模式匹配。 Seq() ++的代码保证(作为其API的一部分),如果它是不可变的,它将不会复制其余部分:

scala> val v = Vector(1,2,3)
v: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3)

scala> Seq() ++ v
res1: Seq[Int] = List(1, 2, 3)

对于某些特殊情况,它可能会模式匹配,但您知道您想要的案例。所以:

def fnc[A](arg: Seq[A]): Seq[A] = arg match {
  case s: collection.immutable.Seq[_] => arg
  case _ => Seq[A]() ++ arg
}

您无需担心_;这只是说你并不关心究竟是什么类型参数(不是你可以检查),如果你这样写,你不会:如果不可变则传递,否则复制。

答案 1 :(得分:1)

  

此模式的最佳做法是什么?

如果您想保证不变性,最佳做法是制作防御性副本,或者要求immutable.Seq

  

但我不确定_案例。是否保证arg是不可变的.Seq或mutable.Seq?

不一定,但我相信从collection.Seq继承的每个标准库集合也都继承自这两者之一。但是,自定义集合理论上可以仅从collection.Seq继承。有关模式匹配解决方案的改进,请参阅Rex的答案。

  

Scala-Collections是否“足够智能”,我可以随时(没有模式匹配)编写List()++ s,如果不可变则获得相同的实例,如果可变则获得深层复制?

它们似乎在某些案件中,但不在其他案件中,例如:

val immutableSeq = Seq[Int](0, 1, 2)
println((Seq() ++ immutableSeq) eq immutableSeq) // prints true
val mutableSeq = mutable.Seq[Int](0, 1, 2)
println((Seq() ++ mutableSeq) eq mutableSeq) // prints false

其中eq是引用相等。请注意,上述内容也适用于List() ++ s,但正如Rex指出的那样,它不适用于所有集合,例如Vector

答案 2 :(得分:0)

你当然可以超载!例如,这编译好:

object  MIO
{
  import collection.mutable

  def f1[A](s: Seq[A]) = 23
  def f1[A](s: mutable.Seq[A]) = 42

  def f2(s: Seq[_]) = 19
  def f2(s: mutable.Seq[_]) = 37
}

在REPL中:

Welcome to Scala version 2.10.0 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_37).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import rrs.scribble.MIO._; import collection.mutable.Buffer
import rrs.scribble.MIO._
import collection.mutable.Buffer

scala> f1(List(1, 2, 3))
res0: Int = 23

scala> f1(Buffer(1, 2, 3))
res1: Int = 42

scala> f2(List(1, 2, 3))
res2: Int = 19

scala> f2(Buffer(1, 2, 3))
res3: Int = 37