在 GitHub 上編輯此頁面

無選項樣式比對

與 Scala 2 相比,Scala 3 中模式比對的實作大幅簡化。從使用者的角度來看,這表示 Scala 3 產生的模式會容易除錯許多,因為所有變數都會顯示在除錯模式中,且位置也正確保留。

Scala 3 支援 Scala 2 萃取器 的超集。

萃取器

萃取器是公開方法 unapplyunapplySeq 的物件

def unapply(x: T): U
def unapplySeq(x: T): U

其中 T 是任意型別,如果它是受檢物件型別 Scrut 的子型別,則會在呼叫方法前執行型別測試U 遵循 固定元數萃取器可變元數萃取器 中所述的規則。

注意:U 可以是萃取器物件的類型。

unapplyunapplySeq 實際上可以有更通用的簽章,允許領先類型子句,以及任意多個使用子句,在常規術語子句之前和之後,以及最多一個隱含子句在結尾,例如

def unapply[A, B](using C)(using D)(x: T)(using E)(using F)(implicit y: G): U = ???

公開方法 unapply 的萃取器稱為固定元數萃取器,適用於固定元數模式。公開方法 unapplySeq 的萃取器稱為可變元數萃取器,適用於可變元數模式。

固定元數萃取器

固定元數萃取器公開下列簽章(帶有潛在類型、使用和隱含子句)

def unapply(x: T): U

類型 U 符合下列其中一項比對

U 符合類型 R

type R = {
  def isEmpty: Boolean
  def get: S
}

S 符合下列其中一項比對

unapply 的前一種形式優先權較高,而單一比對優先權高於基於名稱的比對

注意:R 中的 S 可以是 U

如果符合下列其中一項條件,固定元數萃取器的使用是不可反駁的

  • U = true
  • 萃取器用作乘積比對
  • U <: RU <: { def isEmpty: false }
  • U = Some[T]

注意:由於相容性原因,最後一項規則是必要的,因為 Some 上的 isEmpty 回傳類型為 Boolean 而不是 false,即使它總是回傳 false

布林比對

  • U =:= Boolean
  • 在完全 0 個樣式上進行模式比對

例如

object Even:
  def unapply(s: String): Boolean = s.size % 2 == 0

"even" match
  case s @ Even() => println(s"$s has an even number of characters")
  case s          => println(s"$s has an odd number of characters")

// even has an even number of characters

產品比對

  • U <: 產品
  • N > 0U 中連續 (val 或無參數 def) _1: P1 ... _N: PN 成員的最大數目
  • 在類型為 P1, P2, ..., PN 的完全 N 個樣式上進行模式比對

例如

class FirstChars(s: String) extends Product:
  def _1 = s.charAt(0)
  def _2 = s.charAt(1)

   // Not used by pattern matching: Product is only used as a marker trait.
  def canEqual(that: Any): Boolean = ???
  def productArity: Int = ???
  def productElement(n: Int): Any = ???

object FirstChars:
  def unapply(s: String): FirstChars = new FirstChars(s)

"Hi!" match
  case FirstChars(char1, char2) =>
    println(s"First: $char1; Second: $char2")

// First: H; Second: i

單一比對

  • 在類型為 S1 個樣式上進行模式比對

例如,其中 Nat <: RS = Int

class Nat(val x: Int):
  def get: Int = x
  def isEmpty = x < 0

object Nat:
  def unapply(x: Int): Nat = new Nat(x)

5 match
  case Nat(n) => println(s"$n is a natural number")
  case _      => ()

// 5 is a natural number

基於名稱的比對

  • SN > 1 個成員,使它們每個都是 val 或無參數 def,並從類型為 P1_1 命名到類型為 PN_N
  • S 沒有滿足前一點的 N+1 個成員,即 N 為最大值
  • 在類型為 P1, P2, ..., PN 的完全 N 個樣式上進行模式比對

例如,其中 U = AlwaysEmpty.type <: RS = NameBased

object MyPatternMatcher:
  def unapply(s: String) = AlwaysEmpty

object AlwaysEmpty:
  def isEmpty = true
  def get = NameBased

object NameBased:
  def _1: Int = ???
  def _2: String = ???

"" match
  case MyPatternMatcher(_, _) => ???
  case _ => ()

可變參數萃取器

可變參數萃取器公開以下簽章 (使用潛在類型、using 和隱含子句)

def unapplySeq(x: T): U

其中 U 必須滿足以下條件

  1. 設定 V := U
  2. 如果 V 符合以下比對之一,則 V 有效
  1. 否則 U 必須符合類型 R
type R = {
  def isEmpty: Boolean
  def get: S
}
  1. 設定 V := S,並重新嘗試 2,如果失敗,則 U 無效。

unapplySeqV := U 形式具有較高優先順序,而序列比對相對於產品序列比對具有較高優先順序。

注意:這表示如果 V := U 形式有效,則會忽略 isEmpty

如果符合以下條件之一,則可變參數萃取器的使用是不可否認的

  • 萃取器直接用作序列比對或產品序列比對
  • U <: RU <: { def isEmpty: false }
  • U = Some[T]

注意:由於相容性原因,最後一項規則是必要的,因為 Some 上的 isEmpty 回傳類型為 Boolean 而不是 false,即使它總是回傳 false

注意:小心,根據第一個條件和上面的說明,可以用 def isEmpty: true 定義一個不可否認的萃取器。這強烈不建議,如果在野外發現,幾乎可以肯定是一個錯誤。

序列比對

  • V <: X
type X = {
  def lengthCompare(len: Int): Int // or, `def length: Int`
  def apply(i: Int): T1
  def drop(n: Int): scala.Seq[T2]
  def toSeq: scala.Seq[T3]
}
  • T2T3 符合 T1
  • 模式比對完全 N 個簡單模式,類型為 T1, T1, ..., T1,其中 N 為序列的執行時期大小,或
  • 模式比對 >= N 個簡單模式和一個可變參數模式 (例如,xs: _*),類型為 T1, T1, ..., T1, Seq[T1],其中 N 為序列的最小大小。

例如,其中 V = SU = Option[S] <: RS = Seq[Char]

object CharList:
  def unapplySeq(s: String): Option[Seq[Char]] = Some(s.toList)

"example" match
  case CharList(c1, c2, c3, c4, _, _, _) =>
    println(s"$c1,$c2,$c3,$c4")
  case _ =>
    println("Expected *exactly* 7 characters!")

// e,x,a,m

乘積序列比對

  • V <: Product
  • N > 0V 中連續 (val 或不帶參數的 def) _1: P1 ... _N: PN 成員的最大數量
  • PN 符合 Seq Pattern 中定義的簽章 X
  • 模式比對完全 >= N 個模式,前 N - 1 個模式的類型為 P1, P2, ... P(N-1),其餘模式的類型會如 Seq Pattern 中所述決定。

例如,其中 V = SU = Option[S] <: RS = (String, PN) <: ProductPN = Seq[Int]

class Foo(val name: String, val children: Int*)
object Foo:
  def unapplySeq(f: Foo): Option[(String, Seq[Int])] =
    Some((f.name, f.children))

def foo(f: Foo) = f match
  case Foo(name, x, y, ns*) => ">= two children."
  case Foo(name, ns*)       => "< two children."

有進一步簡化的計畫,特別是將乘積比對基於名稱的比對納入單一類型的萃取器中。

類型測試

使用 ClassTag 的抽象類型測試已改為 TypeTest 或別名 Typeable

  • 抽象類型的模式 _: X 需要範圍內的 TypeTest
  • 使用抽象類型的 unapply 的模式 x @ X() 需要範圍內的 TypeTest

有關 TypeTest 的更多詳細資料