[英]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的問題。 一個小注釋: 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*
。
你可以擁有兩個構造函數:
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.