让我们回答这里涉及的不同问题。我确实会推荐这种语法:
case class ExpList(listExp: Expr*) extends Expr
但答案取决于您的编码示例。那么让我们看看如何在模式匹配中使用可变参数,何时使用List,以及 WrappedArray 的问题。
一个小评论:Expr 和ExpList 之间的不一致(有或没有'r'?)在键入和试图记住哪个是哪个时是有问题的 - 坚持一个约定,Exp 足够清晰并且经常使用。
可变参数和模式匹配
让我们首先考虑这个声明:
abstract class Expr
case class ExpList(listExp: Expr*) extends Expr
case class Var(name: String) extends Expr
还有这个代码示例:
val v = Var("a")
val result = for (i <- Seq(ExpList(v), ExpList(v, v), ExpList(v, v, v))) yield (i match {
case ExpList(a) => "Length 1 (%s)" format a
case ExpList(a, b, c, d @ _*) => "Length >= 3 (%s, %s, %s, %s...)" format (a, b, c, d)
case ExpList(args @ _*) => "Any length: " + args
})
result foreach println
产生:
Length 1 (Var(a))
Any length: WrappedArray(Var(a), Var(a))
Length >= 3 (Var(a), Var(a), Var(a), WrappedArray()...)
我在这里使用的:
ExpList(a, b) 匹配一个有两个孩子的 ExpList; ExpList(a) 匹配一个带有一个孩子的 ExpList。
_* 是一个匹配 A 类型的值序列的模式,可以任意长(包括 0)。
我还使用了模式绑定器identifier @ pattern,它允许绑定一个对象,同时还可以使用另一个模式进一步解构它;它们适用于任何模式,而不仅仅是_*。
当使用identifier @ _* 时,identifier 必须输入Seq[A]。
所有这些结构也适用于Seq;但是如果我们在声明中使用Seq,就像这样:
case class ExpList(listExp: Seq[Expr]) extends Expr
相同的 case 子句从(例如)case ExpList(a, b, c, d @ _*) => 更改为 case ExpList(Seq(a, b, c, d @ _*)) =>。所以语法更混乱。
从语法上讲,Expr* 唯一“更难”的是编写以下函数,它从表达式列表构造一个 ExpList:
def f(x: Seq[Expr]) = ExpList(x: _*)
请注意此处(再次)_* 的使用。
List 类
List 在列表头构造函数上进行模式匹配时使用起来很方便,如xs match { case head :: tail => ... case Nil => }。然而,通常这段代码可以使用折叠更紧凑地表达,如果你不是用这种风格编写代码,你不需要使用List。尤其是在界面中,最好只要求您的代码将需要的内容。
可变性
我们上面讨论的内容涉及不变性。案例类的实例应该是不可变的。现在,当使用Expr*时,case类的参数实际上有collection.Seq[Expr]类型,并且这个类型包含可变实例——实际上,ExprList会接收到子类WrappedArray的一个实例,它是可变的。注意collection.Seq是collection.mutable.Seq和collection.immutable.Seq的超类,后者默认别名为Seq。
如果不向下转换,就无法改变这样的值,但仍然有人可以这样做(我不知道是什么原因)。
如果你想阻止你的客户这样做,你需要将你收到的值转换成一个不可变的序列——但是当你用case class ExpList(listExp: Expr*) extends Expr声明ExpList时你不能这样做。
您需要使用另一个构造函数。
要在其他代码中进行转换,由于toSeq 返回原始序列,因此您必须使用列表内容作为可变参数调用Seq 的构造函数。因此,您使用我上面显示的语法Seq(listExpr: _*)。
目前这并不重要,因为Seq 的默认实现是List,但将来可能会改变(可能会更快,谁知道?)。
擦除问题
不能声明同一方法的两个重载,一个采用T*,一个采用Seq[T],因为在输出类中它们会变得相同。一个小技巧可以让 m 看起来不一样,并且可以使用两个构造函数:
case class ExpList(listExp: Seq[Expr]) extends Expr
object ExpList {
def apply(listExp: Expr*)(implicit d: DummyImplicit) = new ExpList(Seq(listExp: _*))
}
这里我还将数组转换为不可变序列,如上。不幸的是,模式匹配已经完成,就像上面的例子一样,case 类接受Seq[Expr] 而不是Expr*。