【问题标题】:Case classes, pattern matching and varargs案例类、模式匹配和可变参数
【发布时间】:2012-02-09 22:51:44
【问题描述】:

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

abstract class Expr
case class Var(name: String) extends Expr
case class ExpList(listExp: List[Expr]) extends Expr

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

case class ExpList(listExp: Expr*) extends Expr

我想知道,关于模式匹配的每个定义的缺点/优点是什么?

【问题讨论】:

  • 我想对表达式做一些规范化......例如,规范化规则之一是:嵌套列表是扁平化的......

标签: scala pattern-matching


【解决方案1】:

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

case class ExpList(listExp: Expr*) extends Expr

但答案取决于您的编码示例。那么让我们看看如何在模式匹配中使用可变参数,何时使用List,以及 WrappedArray 的问题。 一个小评论:ExprExpList 之间的不一致(有或没有'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 @ _*) =&gt; 更改为 case ExpList(Seq(a, b, c, d @ _*)) =&gt;。所以语法更混乱。

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

def f(x: Seq[Expr]) = ExpList(x: _*)

请注意此处(再次)_* 的使用。

List

List 在列表头构造函数上进行模式匹配时使用起来很方便,如xs match { case head :: tail =&gt; ... case Nil =&gt; }。然而,通常这段代码可以使用折叠更紧凑地表达,如果你不是用这种风格编写代码,你不需要使用List。尤其是在界面中,最好只要求您的代码将需要的内容。

可变性

我们上面讨论的内容涉及不变性。案例类的实例应该是不可变的。现在,当使用Expr*时,case类的参数实际上有collection.Seq[Expr]类型,并且这个类型包含可变实例——实际上,ExprList会接收到子类WrappedArray的一个实例,它是可变的。注意collection.Seqcollection.mutable.Seqcollection.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*

【讨论】:

    【解决方案2】:

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

    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"))
    

    可变参数被转换为mutable.WrappedArray,因此为了符合案例类不可变的约定,您应该使用列表作为实际值。

    【讨论】:

    • 实际上,OP 应该使用不可变序列 - 但我相信 List 经常被误用,因为它们不是通用序列类型。我宁愿接受Seq作为ExpList的参数,定义ExpList.apply返回new ExpList(Seq(listExp: _*))
    • @Blaisorblade 你可以做listExp.toSeq
    • @dhg: 不会给出想要的结果。 WrappedArray[T] &lt;: Seq[T],因此listExp.toSeq 将向上转换listExp。向上转换 listExp 是可能的,但不会阻止有人再次向下转换并修改它。如果您假设没有人这样做,则无需明确执行任何操作,只要将 ExpList 更改为声明为 case class ExpList(listExp: Seq[Expr]) extends Expr,恕我直言,无论如何您都应该这样做。
    • @Blaisorblade 我现在看到了。我假设toSeq 会返回一个immutable.Seq,就像toMap 返回一个immutable.MaptoSet 返回一个'immutable.Set,但事实并非如此。
    • 你们中的哪一位能否提供上述 cmets 的内容作为答案,并涉及更多解释? TY
    【解决方案3】:

    正如对 Dan 解决方案的评论:如果您在函数中有这个,由于 Scala 中的错误无法工作 https://issues.scala-lang.org/browse/SI-3772。你会得到类似的东西:

    scala> :paste
    // Entering paste mode (ctrl-D to finish)
        def g(){
            class Expr {}
            case class ExpList(listExp: List[Expr]) extends Expr
            object ExpList {
              def apply(listExp: Expr*) = new ExpList(listExp.toList)
            }
        }
    // Exiting paste mode, now interpreting.
    
    <console>:10: error: ExpList is already defined as (compiler-generated) case cla
    ss companion object ExpList
                        object ExpList {
                               ^
    

    目前的解决方法就是将对象放在首位。

    scala> :paste
    // Entering paste mode (ctrl-D to finish)
        def g(){
            class Expr {}
            object ExpList {
              def apply(listExp: Expr*) = new ExpList(listExp.toList)
            }
            case class ExpList(listExp: List[Expr]) extends Expr
        }
    
    // Exiting paste mode, now interpreting.
    g: ()Unit
    

    我希望这将防止人们像我一样绊倒这个错误。

    【讨论】:

      猜你喜欢
      • 2011-07-15
      • 2011-01-17
      • 2017-02-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-28
      • 2012-01-02
      • 1970-01-01
      相关资源
      最近更新 更多