案例类,模式匹配和varargs

Pri*_*osK 10 scala pattern-matching

假设我有这样的类层次结构:

abstract class Expr
case class Var(name: String) extends Expr
case class ExpList(listExp: List[Expr]) extends Expr
Run Code Online (Sandbox Code Playgroud)

定义这样的构造函数会更好吗ExpList:

case class ExpList(listExp: Expr*) extends Expr
Run Code Online (Sandbox Code Playgroud)

我想知道,每种定义在模式匹配方面有哪些缺点/好处?

Bla*_*ade 17

让我们回答这里涉及的不同问题.我的确推荐这种语法:

case class ExpList(listExp: Expr*) extends Expr
Run Code Online (Sandbox Code Playgroud)

但答案取决于您的编码示例.那么让我们看看如何在模式匹配中使用varargs,何时使用List以及WrappedArray的问题.一个小小的评论:Expr和/ ExpList(有或没有'r'?)之间的不一致是有问题的,当打字和试图记住哪个 - 坚持一个约定,Exp足够清楚和经常使用.

Varargs和模式匹配

我们首先考虑这个声明:

abstract class Expr
case class ExpList(listExp: Expr*) extends Expr
case class Var(name: String) extends Expr
Run Code Online (Sandbox Code Playgroud)

而这个代码示例:

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
Run Code Online (Sandbox Code Playgroud)

生产:

Length 1 (Var(a))
Any length: WrappedArray(Var(a), Var(a))
Length >= 3 (Var(a), Var(a), Var(a), WrappedArray()...)
Run Code Online (Sandbox Code Playgroud)

我在这里使用的: 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
Run Code Online (Sandbox Code Playgroud)

相同的案例条款从(例如)case ExpList(a, b, c, d @ _*) =>变为case ExpList(Seq(a, b, c, d @ _*)) =>.因此语法更混乱.

从语法上讲,唯一"更难"的Expr*是编写以下函数,它从表达式列表构造一个ExpList:

def f(x: Seq[Expr]) = ExpList(x: _*)
Run Code Online (Sandbox Code Playgroud)

注意这里的使用(再次)_*.

List

List在列表头构造函数上模式匹配时使用很方便,如xs match { case head :: tail => ... case Nil => }.但是,通常使用折叠可以更紧凑地表达此代码,如果您不使用此样式编写代码,则无需使用List.特别是在界面中,通常只需要您的代码所需的代码.

可变性

我们上面讨论过的是不变性.案例类的实例应该是不可变的.现在,在使用时Expr*,case类的参数实际上是类型collection.Seq[Expr],并且此类型包含可变实例 - 实际上,ExprList将接收子类的实例,该实例WrappedArray是可变的.请注意,collection.Seq是两者的超collection.mutable.Seqcollection.immutable.Seq,而后者化名为Seq默认.

如果没有向下转播它就不能改变这样的价值,但是仍然有人可以这样做(我不知道是什么原因).

如果要阻止客户端执行此操作,则需要将接收的值转换为不可变序列 - 但在声明ExpList时不能这样做case class ExpList(listExp: Expr*) extends Expr.

您需要使用另一个构造函数.要在其他代码中进行转换,由于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: _*))
}
Run Code Online (Sandbox Code Playgroud)

在这里,我还将数组转换为不可变序列,如上所述.遗憾的是,模式匹配已完成,如上例中case类接受Seq[Expr]而不是Expr*.


Dan*_*mon 12

你可以拥有两个构造函数:

case class ExpList(listExp: List[Expr]) extends Expr
object ExpList {
  def apply(listExp: Expr*) = new ExpList(listExp.toList)
}

//now you can do
ExpList(List(Var("foo"), Var("bar")))
//or
ExpList(Var("foo"), Var("bar"))
Run Code Online (Sandbox Code Playgroud)

变量参数转换为a mutable.WrappedArray,因此要保持与不可变的案例类的约定一致,您应该使用列表作为实际值.