无选项模式匹配
与 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 <: Product
N > 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 <: Product
N > 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