在 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 <: R 并且 U <: { 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 <: Product
  • N > 0U 中连续(val 或无参数 def_1: P1 ... _N: PN 成员的最大数量
  • 对正好 N 个模式进行模式匹配,类型为 P1, P2, ..., PN

例如

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

单一匹配

  • 1 个模式进行模式匹配,类型为 S

例如,其中 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,并且从 _1(类型为 P1)到 _N(类型为 PN)命名
  • S 没有满足前一点的 N+1 个成员,即 N 是最大的
  • 对正好 N 个模式进行模式匹配,类型为 P1, P2, ..., PN

例如,其中 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 _ => ()

可变元数提取器

可变参数提取器公开以下签名(可能包含类型、使用和隐式子句)

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 <: R 并且 U <: { 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 符合在序列模式中定义的签名 X
  • 对正好 >= N 个模式进行模式匹配,前 N - 1 个模式的类型为 P1, P2, ... P(N-1),其余模式的类型在序列模式中确定。

例如,当 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的更多细节