模式匹配
模式
模式由常量、构造函数、变量和类型测试构成。模式匹配测试给定值(或值序列)是否具有模式定义的形状,如果匹配,则将模式中的变量绑定到值的对应组件(或值序列)。同一个变量名在一个模式中不能被绑定多次。
示例
以下是一些模式示例:
- 模式
ex: IOException
匹配IOException
类的所有实例,将变量ex
绑定到该实例。 - 模式
Some(x)
匹配Some(´v´)
形式的值,将x
绑定到Some
构造函数的参数值 ´v´。 - 模式
(x, _)
匹配值对,将x
绑定到对的第一个组件。第二个组件与通配符模式匹配。 - 模式
x :: y :: xs
匹配长度为 ´\geq 2´ 的列表,将x
绑定到列表的第一个元素,y
绑定到列表的第二个元素,xs
绑定到剩余部分。 - 模式
1 | 2 | 3
匹配 1 到 3 之间的整数。
模式匹配始终在一个提供模式预期类型的上下文中进行。我们区分以下几种模式。
变量模式
变量模式 ´x´ 是一个简单的标识符,以小写字母开头。它匹配任何值,并将变量名绑定到该值。´x´ 的类型是来自外部的模式预期类型。一个特例是通配符模式 _
,它在每次出现时都被视为一个新的变量。
类型模式
一个类型模式 ´x: T´ 由一个模式变量 ´x´ 和一个类型模式 ´T´ 组成。 ´x´ 的类型是类型模式 ´T´,其中每个类型变量和通配符都被替换为一个新的、未知的类型。此模式匹配任何被类型模式 ´T´ 匹配的值;它将变量名绑定到该值。
模式绑定器
一个模式绑定器 ´x´@´p´
由一个模式变量 ´x´ 和一个模式 ´p´ 组成。 变量 ´x´ 的类型是模式 ´p´ 隐含的静态类型 ´T´。 此模式匹配任何被模式 ´p´ 匹配的值 ´v´,并将变量名绑定到该值。
如果模式仅匹配类型 ´T´ 的值,则模式 ´p´ 隐含 类型 ´T´。
字面量模式
一个字面量模式 ´L´ 匹配任何与字面量 ´L´ 相等(在 ==
方面)的值。 ´L´ 的类型必须符合模式的预期类型。
插值字符串模式
模式中插值字符串字面量的扩展与表达式中的相同。 如果它出现在模式中,则以下两种形式的插值字符串字面量
等效于
您可以定义自己的 StringContext
来覆盖 scala
包中的默认 StringContext
。
如果成员 id
评估为一个提取器对象,则此扩展是类型正确的。 如果提取器对象具有 apply
以及 unapply
或 unapplySeq
方法,则处理后的字符串可以用作表达式或模式。
以 XML 为例
然后,XML 模式匹配可以这样表达
其中 linktext 是由模式绑定的变量。
稳定标识符模式
一个稳定标识符模式 是一个稳定标识符 ´r´。 ´r´ 的类型必须符合模式的预期类型。 该模式匹配任何值 ´v´,使得 ´r´ == ´v´
(见这里)。
为了解决与变量模式的语法重叠,稳定标识符模式不能是简单名称,该名称以小写字母开头。 但是,可以将这样的变量名括在反引号中;然后它被视为一个稳定标识符模式。
示例
考虑以下类定义
这里,前三个模式是稳定标识符模式,而最后一个是变量模式。
构造函数模式
一个构造函数模式的形式为 ´c(p_1, ..., p_n)´,其中 ´n \geq 0´。它由一个稳定标识符 ´c´ 组成,后面跟着元素模式 ´p_1, ..., p_n´。构造函数 ´c´ 是一个简单或限定名称,它表示一个 case 类。如果 case 类是单态的,那么它必须符合模式的预期类型,并且 ´x´ 的 主构造函数 的形式参数类型被视为元素模式 ´p_1, ..., p_n´ 的预期类型。如果 case 类是多态的,那么它的类型参数将被实例化,以便 ´c´ 的实例化符合模式的预期类型。然后,´c´ 的主构造函数的实例化形式参数类型被视为组件模式 ´p_1, ..., p_n´ 的预期类型。该模式匹配所有从构造函数调用 ´c(v_1, ..., v_n)´ 创建的对象,其中每个元素模式 ´p_i´ 匹配相应的值 ´v_i´。
当 ´c´ 的形式参数类型以重复参数结尾时,会出现一种特殊情况。这将在 这里 进一步讨论。
元组模式
一个元组模式 (´p_1´, ..., ´p_n´)
,其中 ´n \geq 2´ 等价于 ´p_1´ *: ... *: ´p_n´ *: scala.EmptyTuple
。
备注
()
等价于_: scala.Unit
,而不是scala.EmptyTuple
。(´pat´)
是一个匹配 ´pat´ 的模式,而不是´pat´ *: scala.EmptyTuple
。- 由于带有
*:
的模式很慢,因此可以实现更有效的翻译。例如,(´p_1´, ´p_2´)
可以被翻译成scala.Tuple2(´p_1´, ´p_2´)
,这实际上等价于´p_1´ *: ´p_2´ *: scala.EmptyTuple
。
提取器模式
一个提取器模式 ´x(p_1, ..., p_n)´,其中 ´n \geq 0´,与构造函数模式具有相同的语法形式。但是,它不是 case 类,而是稳定标识符 ´x´ 表示一个对象,该对象具有名为 unapply
或 unapplySeq
的成员方法,该方法与模式匹配。
提取器模式不能匹配值 null
。实现确保 unapply
/unapplySeq
方法不会应用于 null
。
如果一个类型具有一个返回值类型为 T
的 get
方法和一个返回值类型符合 Boolean
的 isEmpty
方法,则称该类型为类型 T
的提取器类型。Option[T]
是类型 T
的提取器类型。
对象 ´x´ 中的 unapply
方法匹配模式 ´x(p_1, ..., p_n)´,如果它只有一个参数(以及可选的隐式参数列表),并且满足以下条件之一:
- ´n=0´ 并且
unapply
的结果类型符合Boolean
。在这种情况下,提取器模式匹配所有´x´.unapply(´v´)
返回true
的值 ´v´。 - ´n=1´ 并且
unapply
的结果类型是某个类型 ´T´ 的提取器类型。在这种情况下,唯一的参数模式 ´p_1´ 的预期类型为 ´T´。提取器模式匹配所有´x´.unapply(´v´)
返回一个值 ´u´,并且´u´.isEmpty
返回false
,´u´.get
返回一个值 ´v_1´,并且 ´p_1´ 匹配 ´v_1´ 的值 ´v´。 - ´n>1´ 并且
unapply
的结果类型是某个类型 ´T´ 的提取器类型,该类型具有返回类型 ´T_1, ..., T_n´ 的成员 ´_1, ..., _n´。在这种情况下,参数模式 ´p_1, ..., p_n´ 的预期类型分别为 ´T_1 , ..., T_n´。提取器模式匹配所有´x´.unapply(´v´)
返回一个值 ´u´,并且´u´.isEmpty
返回false
,´u´.get
返回某个值 ´t´,并且每个模式 ´p_i´ 匹配来自 ´t._1, ..., t._n´ 的相应值 ´t._1´。
对象 ´x´ 中的 unapplySeq
方法匹配模式 ´x(q_1, ..., q_m, p_1, ..., p_n)´,如果它只接受一个参数,并且它的结果类型为 Option[(´T_1, ..., T_m´, Seq[S])]
(如果 m = 0
,则也接受类型 Option[Seq[S]]
)。这种情况将在 下面 进一步讨论。
示例 1
如果我们定义一个提取器对象 Pair
这意味着名称 Pair
可以用于代替 Tuple2
来进行元组的形成以及在模式中对元组进行解构。因此,以下操作是可能的
示例 2
如果我们定义一个类 NameBased
那么 NameBased
本身就是一个 NameBased
的提取器类型,因为它有一个名为 isEmpty
的成员,返回一个布尔类型的值,并且它有一个名为 get
的成员,返回一个 NameBased
类型的的值。
由于它还具有 _1
和 _2
成员,因此它可以在 n = 2 的提取器模式中使用,如下所示
模式序列
模式序列 ´p_1, ..., p_n´ 出现在两种情况下。首先,在构造函数模式 ´c(q_1, ..., q_m, p_1, ..., p_n)´ 中,其中 ´c´ 是一个具有 ´m+1´ 个主构造函数参数的案例类,以一个 重复参数 结束,该参数的类型为 S*
。其次,在提取器模式 ´x(q_1, ..., q_m, p_1, ..., p_n)´ 中,如果提取器对象 ´x´ 没有 unapply
方法,但它定义了一个 unapplySeq
方法,其结果类型是类型 (T_1, ... , T_m, Seq[S])
的提取器类型(如果 m = 0
,则类型 Seq[S]
的提取器类型也被接受)。模式 ´p_i´ 的预期类型为 ´S´。
模式序列中的最后一个模式可以是序列通配符 _*
。每个元素模式 ´p_i´ 都使用 ´S´ 作为预期类型进行类型检查,除非它是序列通配符。如果存在最终的序列通配符,则模式匹配所有以匹配模式 ´p_1, ..., p_{n-1}´ 的元素开头的序列值 ´v´。如果没有给出最终的序列通配符,则模式匹配所有长度为 ´n´ 的序列值 ´v´,这些序列值由匹配模式 ´p_1, ..., p_n´ 的元素组成。
中缀操作模式
中缀操作模式 ´p;\mathit{op};q´ 是构造函数或提取器模式 ´\mathit{op}(p, q)´ 的简写。模式中运算符的优先级和结合性与 表达式 中的相同。
中缀操作模式 ´p;\mathit{op};(q_1, ..., q_n)´ 是构造函数或提取器模式 ´\mathit{op}(p, q_1, ..., q_n)´ 的简写。
模式备选
一个模式备选´p_1´ | ... | ´p_n´
由多个备选模式 ´p_i´ 组成。所有备选模式都将使用模式的预期类型进行类型检查。它们不能绑定除通配符以外的变量。如果至少一个备选模式匹配值 ´v´,则备选模式匹配值 ´v´。
XML 模式
XML 模式在这里进行处理。
正则表达式模式
从 Scala 2.0 版本开始,正则表达式模式已在 Scala 中弃用。
Scala 的更高版本提供了简化版的正则表达式模式,涵盖了大多数非文本序列处理场景。序列模式是一种模式,它位于以下位置之一:(1)预期类型为 T
的模式,该类型符合 Seq[A]
(其中 A
为某个类型),或(2)具有迭代形式参数 A*
的案例类构造函数。最右侧位置的通配符星号模式 _*
代表任意长度的序列。它可以使用 @
绑定到变量,如往常一样,在这种情况下,变量的类型将为 Seq[A]
。
不可反驳模式
如果以下情况之一适用,则模式 ´p´ 对于类型 ´T´ 是不可反驳的
- ´p´ 是一个变量模式,
- ´p´ 是一个类型化模式 ´x: T'´,并且 ´T <: T'´,
- ´p´ 是一个构造函数模式 ´c(p_1, ..., p_n)´,类型 ´T´ 是类 ´c´ 的实例,类型 ´T´ 的主构造函数 具有参数类型 ´T_1, ..., T_n´,并且每个 ´p_i´ 对于 ´T_i´ 都是不可反驳的。
- ´p´ 是一个提取器模式,其中提取器类型为
Some[´T´]
(其中 ´T´ 为某个类型) - ´p´ 是一个提取器模式,其中提取器类型
isEmpty
方法为单例类型false
- ´p´ 是一个提取器模式,其中返回类型为单例类型
true
类型模式
类型模式由类型、类型变量和通配符组成。类型模式 ´T´ 具有以下形式之一
- 对类 ´C´、´p.C´ 或
´T´#´C´
的引用。此类型模式匹配给定类的任何非空实例。请注意,如果存在,类的前缀与确定类实例相关。例如,模式 ´p.C´ 仅匹配以 ´p´ 作为前缀创建的类 ´C´ 的实例。这也适用于未在语法上给出的前缀。例如,如果 ´C´ 指的是在最近的封闭类中定义的类,因此等效于 ´this.C´,则它被认为具有前缀。
底部类型 scala.Nothing
和 scala.Null
不能用作类型模式,因为它们在任何情况下都不会匹配任何内容。
单例类型
´p´.type
。此类型模式仅匹配由路径 ´p´ 表示的值(使用eq
方法将匹配的值与 ´p´ 进行比较)。字面量类型
´lit´
。此类型模式仅匹配由字面量 ´lit´ 表示的值(使用==
方法将匹配的值与 ´lit´ 进行比较)。复合类型模式
´T_1´ with ... with ´T_n´
,其中每个 ´T_i´ 都是一个类型模式。此类型模式匹配所有被每个类型模式 ´T_i´ 匹配的值。参数化类型模式 ´T[a_1, ..., a_n]´,其中 ´a_i´ 是类型变量模式或通配符
_
。此类型模式匹配所有在类型变量和通配符的任意实例化下匹配 ´T´ 的值。这些类型变量的边界或别名类型如 这里 所述确定。参数化类型模式
scala.Array´[T_1]´
,其中 ´T_1´ 是一个类型模式。此类型模式匹配任何非空类型scala.Array´[U_1]´
的实例,其中 ´U_1´ 是被 ´T_1´ 匹配的类型。
不属于上述形式之一的类型也被接受为类型模式。但是,此类类型模式将被转换为其 擦除。Scala 编译器将为这些模式发出“未经检查”警告,以标记可能发生的类型安全丢失。
类型变量模式 是一个以小写字母开头的简单标识符。
模式中的类型参数推断
类型参数推断是为带类型模式或构造函数模式中的绑定类型变量查找边界的过程。推断会考虑模式的预期类型。
带类型模式的类型参数推断
假设一个带类型模式 ´p: T'´。令 ´T´ 来自 ´T'´,其中 ´T'´ 中的所有通配符都被重命名为新的变量名。令 ´a_1, ..., a_n´ 为 ´T´ 中的类型变量。这些类型变量被认为在模式中是绑定的。令模式的预期类型为 ´\mathit{pt}´。
类型参数推断首先构造一个关于类型变量 ´a_i´ 的子类型约束集。初始约束集 ´\mathcal{C}_0´ 仅反映这些类型变量的边界。也就是说,假设 ´T´ 具有绑定类型变量 ´a_1, ..., a_n´,它们对应于类类型参数 ´a_1', ..., a_n'´,具有下界 ´L_1, ..., L_n´ 和上界 ´U_1, ..., U_n´,´\mathcal{C}_0´ 包含以下约束
$$ \begin{cases} a_i &<: \sigma U_i & \quad (i = 1, ..., n) \\ \sigma L_i &<: a_i & \quad (i = 1, ..., n) \end{cases} $$
其中 ´\sigma´ 是替换 ´[a_1' := a_1, ..., a_n' :=a_n]´。
然后通过进一步的子类型约束来扩充集合 ´\mathcal{C}_0´。有两种情况。
情况 1
如果存在一个关于类型变量 `a_1, ..., a_n` 的替换 `\sigma`,使得 `\sigma T` 符合 `\mathit{pt}`,则确定关于类型变量 `a_1, ..., a_n` 的最弱子类型约束 `\mathcal{C}_1`,使得 `\mathcal{C}_0 \wedge \mathcal{C}_1` 意味着 `T` 符合 `\mathit{pt}`。
情况 2
否则,如果 `T` 无法通过实例化其类型变量来使其符合 `\mathit{pt}`,则确定 `\mathit{pt}` 中所有定义为包含该模式的方法的类型参数的类型变量。令这些类型参数的集合为 `b_1 , ..., b_m`。令 `\mathcal{C}_0'` 为反映类型变量 `b_i` 范围的子类型约束。如果 `T` 表示最终类的实例类型,则令 `\mathcal{C}_2` 为关于类型变量 `a_1, ..., a_n` 和 `b_1, ..., b_m` 的最弱子类型约束集,使得 `\mathcal{C}_0 \wedge \mathcal{C}_0' \wedge \mathcal{C}_2` 意味着 `T` 符合 `\mathit{pt}`。如果 `T` 不表示最终类的实例类型,则令 `\mathcal{C}_2` 为关于类型变量 `a_1, ..., a_n` 和 `b_1, ..., b_m` 的最弱子类型约束集,使得 `\mathcal{C}_0 \wedge \mathcal{C}_0' \wedge \mathcal{C}_2` 意味着可以构造一个同时符合 `T` 和 `\mathit{pt}` 的类型 `T'`。如果不存在满足此属性的约束集 `\mathcal{C}_2`,则为静态错误。
最后一步是为类型变量选择类型边界,以暗示已建立的约束系统。对于上述两种情况,该过程有所不同。
情况 1
我们取 `a_i >: L_i <: U_i`,其中每个 `L_i` 都是最小的,每个 `U_i` 都是最大的,相对于 `<:`,使得 `a_i >: L_i <: U_i` 对于 `i = 1, ..., n` 意味着 `\mathcal{C}_0 \wedge \mathcal{C}_1`。
情况 2
我们取 `a_i >: L_i <: U_i` 和 `b_i >: L_i' <: U_i'`,其中每个 `L_i` 和 `L_j'` 都是最小的,每个 `U_i` 和 `U_j'` 都是最大的,使得 `a_i >: L_i <: U_i` 对于 `i = 1, ..., n` 和 `b_j >: L_j' <: U_j'` 对于 `j = 1, ..., m` 意味着 `\mathcal{C}_0 \wedge \mathcal{C}_0' \wedge \mathcal{C}_2`。
在这两种情况下,都允许局部类型推断来限制推断边界的复杂性。类型的最小性和最大性必须相对于可接受复杂度的类型集来理解。
构造函数模式的类型参数推断
假设一个构造函数模式 `C(p_1, ..., p_n)`,其中类 `C` 具有类型参数 `a_1, ..., a_n`。这些类型参数的推断方式与类型化模式 `(_: ´C[a_1, ..., a_n]´) ` 相同。
示例
考虑程序片段
这里,类型模式 `List[a]` 与预期类型 `Any` 匹配。该模式绑定了类型变量 `a`。由于 `List[a]` 对于每个类型参数都符合 `Any`,因此 `a` 没有约束。因此,`a` 被引入为一个没有边界的抽象类型。`a` 的作用域是其 case 子句的右侧。
另一方面,如果x
被声明为
这会生成约束List[a] <: List[List[String]]
,简化为a <: List[String]
,因为List
是协变的。因此,a
被引入,其上限为List[String]
。
示例
考虑程序片段
Scala 在运行时不维护有关类型参数的信息,因此无法检查x
是否为字符串列表。相反,Scala 编译器将擦除模式为List[_]
;也就是说,它只会测试值x
的顶层运行时类是否符合List
,如果符合,则模式匹配将成功。这可能会导致以后出现类转换异常,在列表x
包含除字符串以外的元素的情况下。Scala 编译器将使用“未经检查”的警告消息标记这种潜在的类型安全丢失。
示例
考虑程序片段
模式y: Number
的预期类型为Term[B]
。类型Number
不符合Term[B]
;因此,上述规则的第 2 种情况适用。这意味着B
被视为另一个类型变量,对其进行推断子类型约束。在我们的例子中,适用的约束是Number <: Term[B]
,这意味着B = Int
。因此,在 case 子句中,B
被视为具有下限和上限Int
的抽象类型。因此,case 子句的右侧y.n
(类型为Int
)被发现符合方法声明的结果类型Number
。
模式匹配表达式
一个模式匹配表达式
由选择器表达式´e´和´n > 0´个 case 组成。每个 case 由一个(可能带守卫的)模式´p_i´和一个块´b_i´组成。每个´p_i´可能由一个守卫if ´e´
补充,其中´e´是一个布尔表达式。模式变量在´p_i´中的作用域包括模式的守卫和相应的块´b_i´。
令´T´为选择器表达式´e´的类型,令´a_1, ..., a_m´为所有包含模式匹配表达式的函数的类型参数。对于每个´a_i´,令´L_i´为其下限,´U_i´为其上限。每个模式´p \in {p_1,, ..., p_n}´可以用两种方式进行类型化。首先,尝试使用´T´作为其预期类型对´p´进行类型化。如果失败,则使用修改后的预期类型´T'´对´p´进行类型化,该类型´T'´是通过将´T´中每个类型参数´a_i´替换为undefined得到的。如果第二步也失败,则会产生编译时错误。如果第二步成功,则令´T_p´为模式´p´作为表达式时的类型。然后确定最小边界´L_11, ..., L_m'´和最大边界´U_1', ..., U_m'´,使得对于所有´i´,´L_i <: L_i'´和´U_i' <: U_i´,并且满足以下约束系统
$$ L_1 <: a_1 <: U_1\;\wedge\;...\;\wedge\;L_m <: a_m <: U_m \ \Rightarrow\ T_p <: T $$
如果找不到这样的边界,则会产生编译时错误。如果找到了这样的边界,则以 ´p´ 开头的模式匹配子句将在以下假设下进行类型检查:每个 ´a_i´ 的下界为 ´L_i'´ 而不是 ´L_i´,上界为 ´U_i'´ 而不是 ´U_i´。
每个代码块 ´b_i´ 的预期类型是整个模式匹配表达式的预期类型。如果没有预期类型,则尝试对所有代码块 ´b_i´ 列表进行 协调。模式匹配表达式的类型是所有代码块 ´b_i´ 在协调后的类型 最小上界。
当将模式匹配表达式应用于选择器值时,会按顺序尝试模式,直到找到一个与 选择器值 匹配的模式。假设这种情况是 case ´p_i \Rightarrow b_i´
。整个表达式的结果是评估 ´b_i´ 的结果,其中 ´p_i´ 的所有模式变量都绑定到选择器值的相应部分。如果找不到匹配的模式,则会抛出 scala.MatchError
异常。
case 中的模式也可以后跟一个保护后缀 if e
,其中 ´e´ 是一个布尔表达式。如果 case 中的先前模式匹配,则会评估保护表达式。如果保护表达式评估为 true
,则模式匹配成功。如果保护表达式评估为 false
,则 case 中的模式被认为不匹配,并且继续搜索匹配的模式。
为了提高效率,模式匹配表达式的评估可能会尝试以与文本顺序不同的顺序尝试模式。这可能会影响通过保护中的副作用进行的评估。但是,保证只有在保护它的模式匹配时才会评估保护表达式。
如果模式匹配的选择器是 sealed
类、联合类型 或它们的组合的实例,则模式匹配的编译可能会发出警告,诊断给定模式集不完整,即在运行时可能引发 MatchError
。
示例
考虑以下算术项的定义
有一些项表示数字字面量、增量、零测试和条件。每个项都带有类型参数,表示它所代表的表达式的类型(Int
或 Boolean
)。
可以编写如下类型安全的评估器来评估这些项。
请注意,评估器充分利用了通过模式匹配可以为封闭方法的类型参数获取新边界的这一事实。
例如,第二种情况中模式的类型,Succ(u)
,是Int
。只有当我们假设T
的上限和下限为Int
时,它才符合选择器类型T
。在假设Int <: T <: Int
下,我们也可以验证第二种情况的类型右侧,Int
符合其预期类型T
。
模式匹配匿名函数
匿名函数可以通过一系列情况来定义
这些情况作为表达式出现,没有先前的match
。这种表达式的预期类型必须部分定义。它必须是scala.Function´k´[´S_1, ..., S_k´, ´R´]
(对于某些´k > 0´),或者scala.PartialFunction[´S_1´, ´R´]
,其中参数类型´S_1, ..., S_k´ 必须完全确定,但结果类型´R´ 可能未确定。
如果预期类型是SAM 可转换 为scala.Function´k´[´S_1, ..., S_k´, ´R´]
,则表达式被视为等效于匿名函数
这里,每个´x_i´都是一个新的名称。如这里所示,这个匿名函数又等效于以下实例创建表达式,其中´T´ 是所有´b_i´ 类型 的最小上界。
如果预期类型是scala.PartialFunction[´S´, ´R´]
,则表达式被视为等效于以下实例创建表达式
这里,´x´ 是一个新的名称,´T´ 是所有´b_i´ 类型 的最小上界。如果模式´p_1, ..., p_n´ 中已经有一个变量或通配符模式,则isDefinedAt
方法中的最终默认情况将被省略。
示例
以下是一个使用foldLeft
计算两个向量标量积的示例
此代码中的情况子句等效于以下匿名函数