本节内容:
- 6.4.1 介绍
- 6.4.2 枚举匹配
- 6.4.3 变量捕获
- 6.4.4 结构匹配
- 6.4.5 数组匹配
- 6.4.6 Or 模式
- 6.4.7 守护
- 6.4.8 多个值的匹配
- 6.4.9 提取器
- 6.4.10 穷尽性检查
- 6.4.11 无效的模式检查
模式匹配是根据一个匹配的指定值进行分支处理,尽可能深的模式。在Haxe中,所有的模式匹配通过 switch表达式(第5.17节)处理,在其中通过个体的case语句表示模式。这里我们将探索不同的模式的语法,使用这个数据结构作为运行例子:
Pattern matching is the process of branching depending on a value matching given, possibly deep patterns. In Haxe, all pattern matching is done within a switch expression (5.17) where the individual case expressions represent the patterns. Here we will explore the syntax for different patterns using this data structure as running example:
enum Tree<T> {
Leaf(v:T);
Node(l:Tree<T>, r:Tree<T>);
}
一些模式匹配器基础包括:
Some pattern matcher basics include:
- 模式总是从头至尾匹配
- 最上面的匹配了输入的值的模式,它的表达式将被执行
- 一个 _ 匹配任何,所以 case _: 是等同于 default: 的
- Patterns will always be matched from top to bottom.
- The topmost pattern that matches the input value has its expression executed.
- A _ pattern matches anything, so case _: is equal to default:
枚举可以以一个自然的方式通过它们的构造器进行匹配:
Enums can be matched by their constructors in a natural way:
var myTree = Node(Leaf("foo"), Node(Leaf("bar"), Leaf("foobar")));
var match = switch(myTree) {
// matches any Leaf
case Leaf(_): "0";
// matches any Node that has r = Leaf
case Node(_, Leaf(_)): "1";
// matches any Node that has has
// r = another Node, which has
// l = Leaf("bar")
case Node(_, Node(Leaf("bar"), _)): "2";
// matches anything 12 case _: "3";
}
trace(match); // 2
模式匹配器会检查每个 case ,从头至尾,采用第一个匹配输入值的 case 。下面关于case规则的说明解释帮助你理解该过程:
The pattern matcher will check each case from top to bottom and pick the first one that matches the input value. The following manual interpretation of each case rule helps understanding the process:
case Leaf():匹配失败,因为 myTree 是一个i额 Node case Node(,Leaf()):匹配失败,因为myTree右侧的子树不是一个Leaf,而是另一个 Node **case Node(,Node(Leaf("bar"),_))**:匹配成功
case _:不被检查,因为前一行已经匹配成功
case Leaf(_): matching fails because myTree is a Node case Node(, Leaf()): matching fails because the right sub-tree of myTree is not a Leaf, but another Node case Node(_, Node(Leaf("bar"), _)): matching succeeds case _: this is not checked here because the previous line matched
可以捕获一个子模式的任何值,通过匹配它对应的一个标识符:
It is possible to catch any value of a sub-pattern by matching it against an identifier:
var myTree = Node(Leaf("foo"), Node(Leaf("bar"), Leaf("foobar")));
var name = switch(myTree) {
case Leaf(s): s;
case Node(Leaf(s), _): s;
case _: "none";
}
trace(name); // foo
这会返回如下之一:
This would return one of the following:
- 如果 myTree 是一个 Leaf,它的name 被返回
- 如果myTree 是一个 Node,并且左侧的子树是一个 Leaf,它的name被返回(这应用在这里,返回“foo”)
- 否则 “none”被返回
- If myTree is a Leaf, its name is returned.
- If myTree is a Node whose left sub-tree is a Leaf,its name is returned (this will apply here, returning "foo").
- Otherwise "none" is returned.
还可以使用 = 来捕获值,这更进一步的匹配:
It is also possible to use = to capture values which are further matched:
var node = switch(myTree) {
case Node(leafNode = Leaf("foo"), _): leafNode;
case x: x;
}
trace(node); // Leaf(foo)
这里 ,如果输入匹配 leafNode 是绑定到 Leaf("foo") 。在所有其他情况,myTree 本身是被返回:case x 工作方式类似于 case _ ,它匹配任何,但是使用一个i额标识名称如 x ,它也会绑定匹配的值到这个变量。
Here, leafNode is bound to Leaf("foo") if the input matches that. In all other cases, myTree itself is returned: case x works similar to case _ in that it matches anything, but with an identifier name like x it also binds the matched value to that variable.
还可以根据匿名结构的字段和实例进行匹配:
It is also possible to match against the fields of anonymous structures and instances:
var myStructure = {
name: "haxe",
rating: "awesome"
};
var value = switch(myStructure) {
case { name: "haxe", rating: "poor" }:
throw false;
case { rating: "awesome", name: n }:
n;
case _:
"no awesome language found";
}
trace(value); // haxe
在第二种情况,如果评定匹配 “awesome”我们绑定匹配的 name 字段 为标识符 n,。当然这个结构也可以被放入前一个例子中的 Tree 来联合结构和枚举匹配。
In the second case we bind the matched name field to identifier n if rating matches "awesome". Of course this structure could also be put into the Tree from the previous example to combine structure and enum matching.
类实例的一个限制是,不能匹配它们父类的字段。
A limitation with regards to class instances is that you cannot match against fields of their parent class.
数组可以使用固定的长度匹配:
Arrays can be matched on fixed length:
var myArray = [1, 6];
var match = switch(myArray) {
case [2, _]: "0";
case [_, 6]: "1";
case []: "2";
case [_, _, _]: "3";
case _: "4";
}
trace(match); // 1
这会输出1,因为 array[1]匹配6,array[0]允许是任何内容。
This will trace 1 because array[1] matches 6, and array[0] is allowed to be anything.
| 操作符用来在模式中描述多个接受的模式:
The | operator can be used anywhere within patterns to describe multiple accepted patterns:
var match = switch(7) {
case 4 | 1: "0";
case 6 | 7: "1";
case _: "2";
}
trace(match); // 1
在一个 or 模式中,如果有一个捕获的变量,它必须也出现在它的子模式中。
If there is a captured variable in an or-pattern, it must appear in both its sub-patterns.
也可以更进一步的限制模式,通过使用 case ... if(condition):
语法:
It is also possible to further restrict patterns with the case ... if(condition): syntax:
var myArray = [7, 6];
var s = switch(myArray) {
case [a, b] if (b > a):
b + ">" +a;
case [a, b]:
b + "<=" +a;
case _: "found something else";
}
trace(s); // 6<=7
第一个情况有一个附加的守护条件 if(b>a) 。只有条件满足case才被采用,否则匹配继续下一个case 。
The first case has an additional guard condition if (b > a). It will only be selected if that condition holds, otherwise matching continues with the next case.
数组语法可以用来匹配多个值:
Array syntax can be used to match on multiple values:
var s = switch [1, false, "foo"] {
case [1, false, "bar"]: "0";
case [_, true, _]: "1";
case [_, false, _]: "2";
}
trace(s); // 2
这和数组匹配非常相似,但是有以下不同:
This is quite similar to usual array matching, but there are differences:
- 元素的量是固定的,所以不接受不同长度的数组
- 不能捕获switch的值到一个变量,如 case x 是不被允许的(case _ 也是)。
- The number of elements is fixed, so patterns of different array length will not be accepted.
- It is not possible to capture the switch value in a variable,i.e. case x isnotallowed(case _ still is).
Haxe 3.1.0以后
Since Haxe 3.1.0
提取器允许应用变换到被匹配的值。这才需要对一个匹配的值在匹配继续之前做一个小的操作时经常用到:
Extractors allow applying transformations to values being matched. This is often useful when a small operation is required on a matched value before matching can continue:
enum Test {
TString(s:String);
TInt(i:Int);
}
class Main {
static public function main() {
var e = TString("fOo");
switch(e) {
case TString(temp):
switch(temp.toLowerCase()) {
case "foo": true;
case _: false;
}
case _: false;
}
}
}
这里我们需要TString 枚举构造器的参数值到一个变量 temp,并使用一个嵌套的 switch到temp.toLowerCase() 。很明显,我们想要匹配如果TString保存一个无论大小写的“foo”则成功。这可以通过提取器简单实现:
Here we have to capture the argument value of the TString enum constructor in a variable temp and use a nested switch on temp.toLowerCase(). Obviously, we want matching to succeed if TString holds a value of "foo" regardless of its casing. This can be simplified with extractors:
enum Test {
TString(s:String);
TInt(i:Int);
}
class Main {
static public function main() {
var e = TString("fOo");
var success = switch(e) {
case TString(_.toLowerCase() => "foo"):
true;
case _:
false;
}
}
}
提取器通过extractorExpression => match 表达式识别。编译器生成的代码类似于前面的例子,但是原生的语法被大大精简。提取器由两部分组成,被 => 操作符分隔:
Extractors are identified by the extractorExpression => match expression. The compiler generates code which is similar to the previous example,but the original syntax was greatly simplified. Extractors consist of two parts, which are separated by the => operator:
- 左侧可以使任何的表达式,中间出现的所有下划线被替换为当前匹配的值。
- 右侧是一个模式,匹配的左侧执行的结果。
- The left side can be any expression,where all occurrences of underscore_are replaced with the currently matched value.
- The right side is a pattern which is matched against the result of the evaluation of the left side.
因为右侧是模式,它可以包含另外的提取器。下面的例子链接了两个提取器:
Since the right side is a pattern, it can contain another extractor. The following example “chains” two extractors:
class Main {
static public function main() {
switch(3) {
case add(_, 1) => mul(_, 3) => a:
trace(a);
}
}
static function add(i1:Int, i2:Int) {
return i1 + i2;
}
static function mul(i1:Int, i2:Int) {
return i1 * i2;
}
}
这里输出 12 作为调用 add(3,1)的结果,3是匹配的值,mul(4,3)中4是add调用的结果。需要注意的是,a在第二个 =>操作符的右侧是一个捕获的变量(第6.4.3节)。
This traces 12 as a result of the calls to add(3, 1), where 3 is the matched value, and mul(4, 3) where 4 is the result of the add call. It is worth noting that the a on the right side of the second => operator is a capture variable (6.4.3).
当前不能在 or 模式中使用提取器:
It is currently not possible to use extractors within or-patterns (6.4.6):
class Main {
static public function main() {
switch("foo") {
// Extractors in or patterns are not allowed
case (_.toLowerCase() => "foo") | "bar":
}
}
}
然而,可以把or模式用在提取器的右侧,所以前面的例子会编译而没有括号。
However, it is possible to have or-patterns on the right side of an extractor, so the previous example would compile without the parentheses.
编译器确保没有被遗漏的可能的case:
The compiler ensures that no possible cases are forgotten:
switch(true) {
case false:
} // Unmatched patterns: true
匹配的类型 Bool 认可两个值 true 和false ,但是只有false被检查。
The matched type Bool admits two values true and false, but only false is checked.
以类似的方式,编译器检测不会匹配输入的值的模式:
In a similar fashion, the compiler detects patterns which will never match the input value:
switch(Leaf("foo")) {
case Leaf(_)
| Leaf("foo"): // This pattern is unused
case Node(l,r):
case _: // This pattern is unused
}