2.31. 模式匹配

在计算机编程领域,有一个称为模式匹配的概念。 这种技术允许我们获取一个复杂的值,例如数组或变体,并将其与一组模式进行比较。 如果该值符合某个模式,则匹配过程将继续,我们可以从该值中提取特定值。 这是一个强大的工具,可以提高我们的代码的可读性和效率。 在本节中,我们将探索在 Daslang 中使用模式匹配的不同方式。

在 Daslang 中,模式匹配是通过 daslib/match 模块中的宏实现的。

2.31.1. Enumeration Matching

Daslang 支持对枚举进行模式匹配,这允许您将枚举的值与特定模式匹配。 您可以使用此功能来简化代码,因为无需多个 if-else 语句或 switch 语句。 要匹配 Daslang 中的枚举,请使用 match 关键字,后跟枚举值和一系列 if 语句,每个语句表示要匹配的模式。如果找到匹配项,则执行相应的代码块。

例:

enum Color {
    Black
    Red
    Green
    Blue
}

def enum_match (color:Color) {
    match ( color ) {
        if ( Color Black ) {
            return 0
        }
        if ( Color Red ) {
            return 1
        }
        if ( _ ) {
            return -1
        }
    }
}

在此示例中,enum_match 函数将 Color 枚举值作为参数,并根据匹配的模式返回一个值。 if Color Black 语句与 Black 枚举值匹配,if Color Red 语句与 Red 枚举值匹配,if _ 语句是与尚未显式匹配的任何其他枚举值匹配的 catch-all。

2.31.2. 匹配变体

可以使用 match 语句匹配 Daslang 中的变体。 变体是一种可区分联合类型,它包含多个可能的值之一,每个值都是不同的类型。

在此示例中,IF 变体有两个可能的值:int 类型的 i 和 float 类型的 f。 variant_as_match 函数将 IF 类型的值作为参数,并对其进行匹配以确定其类型。

if _ as i 语句匹配任何值,并将其分配给声明的变量 i。 同样,if _ as f 语句匹配任何值并将其分配给声明的变量 f。 最后的 if _ 语句匹配任何剩余值,并返回 “anything”。

例:

variant IF {
    i : int
    f : float
}

def variant_as_match (v:IF) {
    match ( v ) {
        if ( _ as i ) {
            return "int"
        }
        if ( _ as f ) {
            return "float"
        }
        if ( _ ) {
            return "anything"
        }
    }
}

在 Daslang 中,可以使用用于创建新变体的相同语法来匹配变体。

下面是一个示例:

def variant_match (v : IF) {
    match ( v ) {
        if ( IF(i=$v(i)) ) {
            return 1
        }
        if ( IF(f=$v(f)) ) {
            return 2
        }
        if ( _ ) {
            return 0
        }
    }
}

在上面的示例中,函数 variant_match 采用 IF 类型的变体 v。第一种情况如果包含 i 并将 i 的值绑定到变量 i,则匹配 v。 在本例中,该函数返回 1。第二种情况如果包含 f 并将 f 的值绑定到变量 f,则匹配 v。在这种情况下,该函数返回 2。T 最后一个 case 匹配与前两个 case 不匹配的任何内容,并返回 0。

2.31.3. 在模式匹配中声明变量

在 Daslang 中,您可以在模式匹配语句中声明变量,包括变体匹配。 要声明变量,请使用语法 $v(decl),其中 decl 是要声明的变量的名称。 然后,为声明的变量分配匹配模式的值。

此功能不仅限于变体匹配,可以在 Daslang 中的任何模式匹配语句中使用。 在此示例中,if $v(as_int)语句在保存整数并声明变量 as_int 以存储该值时匹配 variant 值。同样,$v(as_float) 语句在保存浮点值并声明变量 as_float 来存储该值时匹配 variant 值。

示例:

variant IF {
    i : int
    f : float
}

def variant_as_match (v:IF) {
    match ( v ) {
        if ( $v(as_int) as i ) {
            return as_int
        }
        if ( $v(as_float) as f ) {
            return as_float
        }
        if ( _ ) {
            return None
        }
    }
}

2.31.4. 匹配结构体

Daslang 支持使用 match 语句匹配结构。 结构体是一种复合数据类型,它将不同数据类型的变量分组到一个名称下。

在此示例中,Foo 结构具有一个 int 类型的成员 a。 struct_match 函数采用 Foo 类型的参数,并将其与各种模式进行匹配。

如果 [[Foo a=13]] 匹配 a 等于 13 的 Foo 结构,则第一个匹配项,如果匹配成功,则返回 0。 如果 [[Foo a=$v(anyA)]] 匹配任何 Foo 结构并将其成员绑定到声明的变量 anyA,则第二次匹配。 如果成功,则此匹配返回 anyA 的值。

示例:

struct Foo {
    a : int
}

def struct_match (f:Foo) {
    match ( f ) {
        if ( Foo(a=13) ) {
            return 0
        }
        if ( Foo(a=$v(anyA)) ) {
            return anyA
        }
    }
}

2.31.5. 使用守卫

Daslang 支持在其模式匹配机制中使用守卫。 守卫是除了成功的模式匹配之外还必须满足的条件。

在此示例中,AB 结构有两个 int 类型的成员 a 和 b。 guards_match 函数采用 AB 类型的参数,并将其与各种模式进行匹配。

如果 [[AB a=$v(a), b=$v(b)]] && (b > a) 匹配 AB 结构并将其 a 和 b 成员分别绑定到声明的变量 a 和 b,则第一次匹配。 还必须满足守卫条件 b > a 才能使此匹配成功。如果此匹配成功,该函数将返回一个字符串,指示 b 大于 a。

如果 [[AB a=$v(a), b=$v(b)]] 匹配任何 AB 结构,并将其 a 和 b 成员分别绑定到声明的变量 a 和 b,则第二次匹配。 通过警卫对比赛没有额外的限制。如果此匹配成功,该函数将返回一个字符串,指示 b 小于或等于 a。

例:

struct AB {
    a, b : int
}

def guards_match (ab:AB) {
    match ( ab ) {
        if ( AB(a=$v(a), b=$v(b)) && (b > a) ) {
            return "{b} > {a}"
        }
        if ( AB(a=$v(a), b=$v(b)) ) {
            return "{b} <= {a}"
        }
    }
}

2.31.6. 元组匹配

在 Daslang 中匹配元组是通过双方括号完成的,并使用与创建新元组相同的语法。 必须指定 Tuples 的类型,或者可以使用 auto 来指示自动类型推理。

下面是一个演示 Daslang 中的元组匹配的示例:

def tuple_match ( A : tuple<int;float;string> ) {
    match ( A ) {
        if (1,_,"3") {
            return 1
        }
        if (13,...) {      // starts with 13
            return 2
        }
        if (...,"13") {    // ends with "13"
            return 3
        }
        if (2,...,"2") {   // starts with 2, ends with "2"
            return 4
        }
        if ( _ ) {
            return 0
        }
    }
}

在此示例中,tuple<int;float;string> 类型的元组 A 作为参数传递给函数 tuple_match。 该函数使用 match 语句来匹配元组 A 中的不同模式。 match 语句中的 if 子句使用双方括号指定要匹配的模式。

要匹配的第一个模式是 (1,_,”3”)。 该模式匹配以值 1 开头,后跟任何值,以字符串 “3” 结尾的元组。 模式中的 _ 符号表示可以在 Tuples 中的该位置匹配任何值。

要匹配的第二个模式是 (13,…(,它匹配以值 13 开头的元组。 这…符号表示在值 13 之后可以匹配任意数量的值。

要匹配的第三个模式是 (…,”13”),它匹配以字符串 “13” 结尾的元组。 这…符号表示可以在字符串 “13” 之前匹配任意数量的值。

要匹配的第四个模式是 (2,…,”2”),它匹配以值 2 开头并以字符串 “2” 结尾的元组。

如果没有任何模式匹配,则执行 _ 子句,函数返回 0。

2.31.7. 匹配 Static 数组

Daslang 中的静态数组可以使用双方括号语法进行匹配,类似于元组。 此外,静态数组必须指定其类型,或者可以使用 auto 关键字自动推断类型。

下面是一个匹配 int[3] 类型的静态数组的示例:

def static_array_match ( A : int[3] ) {
    match ( A ) {
        if ( fixed_array($v(a),$v(b),$v(c)) && (a+b+c)==6 ) { // 总共 3 个元素,总和为 6
            return 1
        }
        if ( fixed_array(0,...) ) {    // 以 0 开头
            return 0
        }
        if ( fixed_array(..,13) ) {   // 以 13 结尾
            return 2
        }
        if ( fixed_array(12,...,12) ) {    // 以 12 开头和结尾
            return 3
        }
        if ( _ ) {
            return -1
        }
    }
}

在此示例中,函数 static_array_match 采用 int[3] 类型的参数,该参数是一个包含三个整数的静态数组。 match 语句使用双方括号语法来匹配输入数组 A 的不同模式。

第一种情况,fixed_array($v(a),$v(b),$v(c)) && (a+b+c)==6,匹配一个数组,其中三个元素的总和等于6。 匹配的元素使用 $v 语法分配给变量 a、b 和 c。

接下来的三种情况分别匹配以 0 开头、以 13 结尾、以 12 开头和结尾的数组。 这…语法 用于匹配两者之间的任何元素。

最后,_ 大小写匹配任何与任何其他大小写都不匹配的数组,在本例中返回 -1。

2.31.8. 动态数组匹配

动态数组用于存储可在运行时更改的值集合。 在 Daslang 中,可以使用与元组类似的语法将动态数组与模式匹配,但增加了对数组中元素数的检查。

下面是一个对动态整数数组进行匹配的示例:

def dynamic_array_match ( A : array<int> ) {
    match ( A ) {
        if ( [$v(a),$v(b),$v(c)] && (a+b+c)==6 ) { // 总共 3 个元素,总和为 6
            return 1
        }
        if ( [0,0,0,...] ) {    // 前 3 个为 0
            return 0
        }
        if ( [...,1,2] ) {      // 以 1,2 结尾
            return 2
        }
        if ( [0,1;...,2,3] ) {    // 以 0,1 开头,以 2,3 结尾
            return 3
        }
        if ( _ ) {
            return -1
        }
    }
}

在上面的代码中,dynamic_array_match 函数将一个动态整数数组作为参数。 然后,match 语句尝试将数组中的元素与一系列模式进行匹配。

第一种模式如果 [$v(a),$v(b),$v(c)] && (a+b+c)==6 匹配包含三个元素的数组,并且这些元素的总和为 6。 $v 语法用于匹配和捕获数组中元素的值。然后,可以在条件 (a+b+c)==6 中使用捕获的值。

如果 [0,0,0,…] 匹配以三个 0 开头的数组,则第二种模式。这 … 语法用于匹配数组中的任何剩余元素。

如果 […,1,2] 匹配以元素 1 和 2 结尾的数组,则第三种模式。

如果 [0,1,…,2,3] 匹配以元素 0 和 1 开头并以元素 2 和 3 结尾的数组,则第四种模式匹配。

如果 _ 匹配与前面的任何模式不匹配的任何数组,则为最终模式。

请务必注意,动态数组中的元素数必须与模式中的元素数匹配,匹配才会成功。

2.31.9. 匹配表达式

在 Daslang 中,match 表达式允许您重复使用模式中较早声明的变量,以匹配模式中较晚的表达式。

以下示例演示了如何使用 match 表达式来检查整数数组是否按升序排列:

def ascending_array_match ( A : int[3] ) {
    match ( A ) {
        if [$v(x),match_expr(x+1),match_expr(x+2)] ) {
            return true
        }
        if ( _ ) {
            return false
        }
    }
}

在此示例中,数组的第一个元素与 x 匹配。然后,分别使用 match_expr 和表达式 x+1 和 x+2 匹配接下来的两个元素。 如果所有三个元素都匹配,则函数返回 true。如果没有匹配项,该函数将返回 false。

2.31.10. 匹配 ||表达式

在 Daslang 中,您可以使用 || 表达式 按选项的显示顺序匹配它们。当您想要根据多个条件匹配变体时,这非常有用。

以下是使用 ||表达:

struct Bar {
    a : int
    b : float
}

def or_match ( B:Bar ) {
    match ( B ) {
        if ( Bar(a=1,b=$v(b)) || Bar(a=2,b=$v(b)) ) {
            return b
        if ( _ ) {
            return 0.0
        }
    }
}

在此示例中,函数 or_match 采用 Bar 类型的变体 B,并使用 ||表达。 当 a 的值为 1 且 b 被捕获为变量时,第一个选项匹配。 当 a 的值为 2 且 b 被捕获为变量时,第二个选项匹配。 如果这些选项中的任何一个匹配,则返回 b 的值。如果两个选项都不匹配,则返回 0.0。

需要注意的是,对于 || 表达式 才能正常工作,则语句的两端都必须声明相同的变量。

2.31.11. [match_as_is] 结构注释

Daslang 中的 [match_as_is] 结构注释允许您对不同类型的结构执行模式匹配。 这允许在单个模式匹配表达式中匹配不同类型的结构体 只要已为匹配类型实现了必要的 is 和 as 运算符。

下面是如何使用 [match_as_is] 结构注释的示例:

[match_as_is]
struct CmdMove : Cmd {
    override rtti = "CmdMove"
    x : float
    y : float
}

在此示例中,结构 CmdMove 标有 [match_as_is] 注释,允许它参与模式匹配:

def operator is CmdMove ( cmd:Cmd ) {
    return cmd.rtti=="CmdMove"
}

def operator is CmdMove ( anything ) {
    return false
}

def operator as CmdMove ( cmd:Cmd ==const ) : CmdMove const& {
    assert(cmd.rtti=="CmdMove")
    unsafe {
        return reinterpret<CmdMove const&> cmd
    }
}

def operator as CmdMove ( var cmd:Cmd ==const ) : CmdMove& {
    assert(cmd.rtti=="CmdMove")
    unsafe {
        return reinterpret<CmdMove&> cmd
    }
}

def operator as CmdMove ( anything ) {
    panic("Cannot cast to CmdMove")
    return default<CmdMove>
}

def matching_as_and_is (cmd:Cmd) {
    match ( cmd ) {
        if CmdMove(x=$v(x), y=$v(y)) ) {
            return x + y
        }
        if ( _ ) {
            return 0.
        }
    }
}

在此示例中,已为 CmdMove 结构实现了必要的 is 和 as 运算符,以允许其参与模式匹配。is 运算符用于确定类型的兼容性,as 运算符用于执行实际的类型转换。

在 matching_as_and_is 函数中,使用CmdMove(x=$v(x),y=$v(y))模式将 cmd 与 CmdMove 结构进行匹配。如果匹配成功,则提取 x 和 y 的值并返回总和。如果匹配不成功,则匹配 catch-all _ 大小写,并返回 0.0。

注意 仅当已为匹配类型实现必要的 is 和 as 运算符时,[match_as_is] 结构注释才有效。在上面的示例中,已经为 CmdMove 结构实现了必要的 is 和 as 运算符,以允许它参与模式匹配。

2.31.12. [match_copy] 结构注释

Daslang 中的 [match_copy] 结构注释允许您对不同类型的结构执行模式匹配。 这允许在单个模式匹配表达式中匹配不同类型的结构体 只要已为匹配类型实现了必要的 match_copy 函数。

下面是如何使用 [match_copy] 结构注释的示例:

[match_copy]
struct CmdLocate : Cmd {
    override rtti = "CmdLocate"
    x : float
    y : float
    z : float
}

在此示例中,结构 CmdLocate 标有 [match_copy] 注释,允许它参与模式匹配。

match_copy 函数用于匹配不同类型的结构。下面是 CmdLocate 结构的 match_copy 函数的实现示例:

def match_copy ( var cmdm:CmdLocate; cmd:Cmd ) {
    if ( cmd.rtti != "CmdLocate" ) {
        return false
    }
    unsafe {
        cmdm = reinterpret<CmdLocate const&> cmd
    }
    return true
}

在此示例中,match_copy 函数采用两个参数:CmdLocate 类型的 cmdm 和 Cmd 类型的 cmd。 此函数的用途是确定 cmd 参数是否为 CmdLocate 类型。 如果是,该函数将使用 reinterpret 执行到 CmdLocate 的类型强制转换,并将结果分配给 cmdm。 然后,该函数返回 true 以指示类型转换成功。如果 cmd 参数不是 CmdLocate 类型,则函数返回 false。

以下是如何在 matching_copy 函数中使用 match_copy 函数的示例:

def matching_copy ( cmd:Cmd ) {
    match ( cmd ) {
        if ( CmdLocate(x=$v(x), y=$v(y), z=$v(z)) ) {
            return x + y + z
        }
        if ( _ ) {
            return 0.
        }
    }
}

在此示例中,matching_copy 函数采用 Cmd 类型的单个参数 cmd。此函数对 cmd 参数执行类型匹配作以确定其类型。 如果 cmd 参数的类型为 CmdLocate,则该函数返回其 x、y 和 z 字段的值之和。如果 cmd 参数是任何其他类型,则函数返回 0。

注意 仅当为匹配类型实现了必要的 match_copy 函数时,[match_copy] 结构注释才有效。 在上面的示例中,已为 CmdLocate 结构实现了必要的 match_copy 函数,以允许它参与模式匹配。

2.31.13. 静态匹配

静态匹配是一种匹配泛型表达式 Daslang 的方法。它的工作方式与常规匹配类似,但有一个关键区别: 当 match 表达式和模式之间存在类型不匹配时,匹配将在编译时被忽略,而不是编译错误。 这使得静态匹配对于泛型函数来说是稳健的。

静态匹配的语法如下:

static_match ( match_expression ) {
    if ( pattern_1 ) {
        return result_1
    }
    if ( pattern_2 ) {
        return result_2
    }
    ...
    if ( _ ) {
        return result_default
    }
}

这里, match_expression 是要与 patterns 匹配的表达式。每个模式都是match_expression将与之进行比较的值或表达式。 如果 match_expression 匹配其中一个模式,则返回相应的结果。如果没有任何模式匹配,则将返回 result_default。 如果 pattern 无法匹配,则将被忽略。

这是一个例子:

enum Color {
    red
    green
    blue
}

def enum_static_match ( color, blah )
    static_match ( color ) {
        if ( Color red ) {
            return 0
        }
        if ( match_expr(blah) ) {
            return 1
        }
        if ( _ ) {
            return -1
        }
    }
}

在此示例中,颜色与枚举值 red、green 和 blue 进行匹配。如果匹配表达式 color 等于枚举值 red,则返回 0。 如果匹配表达式 color 等于 blah 的值,则将返回 1。如果没有任何模式匹配,将返回 -1。

注意 match_expr 用于将 blah 与匹配表达式颜色匹配,而不是直接将 blah 与枚举值匹配。

如果 color 不是 Color,则第一次匹配将失败。如果 blah 不是 Color,则第二个匹配将失败。但该函数将始终编译。

2.31.14. match_type

Daslang 中的 match_type 子表达式允许您根据表达式的类型执行模式匹配。 它在 static_match 语句中用于指定要匹配的表达式类型。

match_type 的语法如下:

if ( match_type(type<Type>, expr) ) {
    // code to run if match is successful
}

其中 Type 是要匹配的类型,expr 是要匹配的表达式。

下面是如何使用 match_type 子表达式的示例:

def static_match_by_type (what) {
    static_match ( what ) {
        if ( match_type(type<int>,$v(expr)) ) {
            return expr
        }
        if ( _ ) {
            return -1
        }
    }
}

在此示例中,要匹配的表达式是什么。如果 what 的类型为 int,则将其分配给变量 $v 并返回表达式 expr。如果 what 不是 int 类型,则匹配落入 catch-all _ 大小写,并返回 -1。

注意 match_type 子表达式仅匹配类型,不匹配的值将被忽略。这与常规模式匹配相反,在常规模式匹配中,type 和 value 必须匹配才能成功。

2.31.15. 多重匹配

在 Daslang 中,您可以使用 multi_match 功能来匹配单个表达式中的多个值。当您想要根据多个不同的条件匹配值时,这非常有用。

以下是使用 multi_match 功能的示例:

def multi_match_test ( a:int ) {
    var text = "{a}"
    multi_match ( a ) {
        if ( 0 ) {
            text += " zero"
        }
        if ( 1 ) {
            text += " one"
        }
        if ( 2 ) {
            text += " two"
        }
        if ( $v(a) && (a % 2 == 0) && (a!=0) ) {
            text += " even"
        }
        if ( $v(a) && (a % 2 == 1) ) {
            text += " odd"
        }
    }
    return text
}

在此示例中,函数 multi_match_test 采用整数值 a 并使用 multi_match 功能对其进行匹配。 前 3 个选项分别在 a 等于 0、1 或 2 时匹配。 当 a 不等于 0 且为偶数时,第四个选项匹配。 当 a 为奇数时,第五个选项匹配。变量文本将根据匹配条件进行更新。 最终结果将作为 text 的字符串表示形式返回。

请务必注意,multi_match 功能允许在单个表达式中匹配多个条件。 与使用多个 match 和 if 语句相比,这使得代码更简洁、更易于阅读。

使用常规匹配的相同示例如下所示:

def multi_match_test ( a:int ) {
    var text = "{a}"
    match ( a ) {
        if ( 0 ) {
            text += " zero"
        }
    }
    match ( a ) {
        if ( 1 ) {
            text += " one"
        }
    }
    match ( a ) {
        if ( 2 ) {
            text += " two"
        }
    }
    match ( a ) {
        if ( $v(a) && (a % 2 == 0) && (a!=0) ) {
            text += " even"
        }
    }
    match ( a ) {
        if ( $v(a) && (a % 2 == 1) ) {
            text += " odd"
        }
    }
    return text
}

static_multi_matchmulti_match 的变体,可与 static_match 一起使用。