无选项模式匹配
与 Scala 2 相比,Scala 3 中的模式匹配实现得到了极大的简化。从用户的角度来看,这意味着 Scala 3 生成的模式更容易调试,因为所有变量都会在调试模式下显示,并且位置会正确保留。
Scala 3 支持 Scala 2 的超集 提取器.
提取器
提取器是暴露了 unapply 或 unapplySeq 方法的对象
def unapply(x: T): U
def unapplySeq(x: T): U
其中 T 是任意类型,如果它是被匹配对象类型 Scrut 的子类型,则在调用方法之前会执行 类型测试。U 遵循 固定元数提取器 和 可变元数提取器 中描述的规则。
注意: U 可以是提取器对象的类型。
unapply 和 unapplySeq 实际上可以具有更通用的签名,允许使用前导类型子句,以及在常规项子句之前和之后任意多个使用子句,以及最多一个隐式子句在最后,例如
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 <: ProductN > 0是U中连续(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 <: R,S = 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
基于名称的匹配
S有N > 1个成员,它们都是val或无参数def,并且从_1(类型为P1)到_N(类型为PN)命名S没有满足前一点的N+1个成员,即N是最大的- 对正好
N个模式进行模式匹配,类型为P1, P2, ..., PN
例如,其中 U = AlwaysEmpty.type <: R,S = 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必须满足以下条件
- 设置
V := U V有效,如果V符合以下匹配之一
- 否则,
U必须符合类型R
type R = {
def isEmpty: Boolean
def get: S
}
- 设置
V := S,并重新尝试步骤 2,如果失败,则U无效。
unapplySeq 的 V := 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]
}
T2和T3符合T1- 对正好
N个简单模式进行模式匹配,这些模式的类型为T1, T1, ..., T1,其中N是序列的运行时大小,或者 - 对
>= N个简单模式和一个可变参数模式(例如,xs: _*)进行模式匹配,这些模式的类型为T1, T1, ..., T1, Seq[T1],其中N是序列的最小大小。
例如,当 V = S、U = Option[S] <: R、S = 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 <: ProductN > 0是V中连续的(val或无参数def)_1: P1..._N: PN成员的最大数量PN符合在序列模式中定义的签名X- 对正好
>= N个模式进行模式匹配,前N - 1个模式的类型为P1, P2, ... P(N-1),其余模式的类型在序列模式中确定。
例如,当 V = S、U = Option[S] <: R、S = (String, PN) <: Product、PN = 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