簡體   English   中英

案例類,模式匹配和varargs

[英]Case classes, pattern matching and varargs

假設我有這樣的類層次結構:

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

我想知道,每種定義在模式匹配方面有哪些缺點/好處?

讓我們回答這里涉及的不同問題。 我的確推薦這種語法:

case class ExpList(listExp: Expr*) extends Expr

但答案取決於您的編碼示例。 那么讓我們看看如何在模式匹配中使用varargs,何時使用List以及WrappedArray的問題。 一個小注釋: ExprExpList (有或沒有'r'?)之間的不一致是有問題的,當鍵入並試圖記住哪個 - 堅持一個約定, Exp足夠清楚並經常使用。

Varargs和模式匹配

我們首先考慮這個聲明:

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.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*

你可以擁有兩個構造函數:

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 ,因此要保持與不可變的案例類的約定一致,您應該使用列表作為實際值。

正如對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

我希望這會阻止人們像我一樣絆倒這個bug。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM