模式匹配

模式是转换输入数据的规则。 可在 F# 中使用模式将数据与一个或多个逻辑结构进行比较,将数据分解为各个构成部分,或通过各种方式从数据中提取信息。

注解

模式用于许多语言构造,例如 match 表达式。 当你在 let 绑定、Lambda 表达式以及与 try...with 表达式关联的异常处理程序中处理函数的参数时,可使用模式。 有关详细信息,请参阅 Match 表达式let 绑定Lambda 表达式:fun 关键字异常:try...with 表达式

例如,在 match 表达式中,模式是管道符号后面的内容。

match expression with
| pattern [ when condition ] -> result-expression
...

每个模式都充当以某种方式转换输入的规则。 在 match 表达式中,依次检查每个模式以查看输入数据是否与该模式兼容。 如果找到匹配项,则执行结果表达式。 如果未找到匹配项,则测试下一个模式规则。 Match 表达式中介绍了可选的 when condition 部分。

支持的模式如下表所示。 在运行时,系统按照表中列出的顺序针对以下每个模式测试输入,并以递归方式应用模式,即,从出现在代码中的第一个到最后一个,每行上的模式按从左到右的顺序。

“属性” 步骤 示例
常量模式 任何数值、字符或字符串字面量、枚举常量或定义的字面量标识符 1.0, "test", 30, Color.Red
标识符模式 可区分联合、异常标签或活动模式用例的 case 值 Some(x)

Failure(msg)
变量模式 identifier a
as 模式 pattern as identifier (a, b) as tuple1
OR 模式 pattern1 | pattern2 ([h] | [h; _])
AND 模式 pattern1 & pattern2 (a, b) & (_, "test")
Cons 模式 identifier :: list-identifier h :: t
列表模式 [ pattern_1; ... ; pattern_n ] [ a; b; c ]
数组模式 [| pattern_1; ..; pattern_n |] [| a; b; c |]
带括号模式 ( pattern ) ( a )
元组模式 ( pattern_1, ... , pattern_n ) ( a, b )
记录模式 { identifier1 = pattern_1; ... ; identifier_n = pattern_n } { Name = name; }
通配符模式 _ _
模式和类型注释 pattern : type a : int
类型测试模式 :? type [ as identifier ] :? System.DateTime as dt
Null 模式 null null
Nameof 模式 nameof expr nameof str

常量模式

常量模式包括数值、字符和字符串字面量、枚举常量(包括枚举类型名称)。 仅具有常量模式的 match 表达式类似于其他语言中的 case 语句。 将输入与字面量值进行比较,如果值相等,则模式匹配。 字面量的类型必须与输入的类型兼容。

以下示例演示了字面量模式的用法,并且还使用了变量模式和 OR 模式。

[<Literal>]
let Three = 3

let filter123 x =
    match x with
    // The following line contains literal patterns combined with an OR pattern.
    | 1 | 2 | Three -> printfn "Found 1, 2, or 3!"
    // The following line contains a variable pattern.
    | var1 -> printfn "%d" var1

for x in 1..10 do filter123 x

字面量模式的另一个示例是基于枚举常量的模式。 使用枚举常量时,必须指定枚举类型名称。

type Color =
    | Red = 0
    | Green = 1
    | Blue = 2

let printColorName (color:Color) =
    match color with
    | Color.Red -> printfn "Red"
    | Color.Green -> printfn "Green"
    | Color.Blue -> printfn "Blue"
    | _ -> ()

printColorName Color.Red
printColorName Color.Green
printColorName Color.Blue

标识符模式

如果模式是构成有效标识符的字符串,那么,标识符的形式决定了模式的匹配方式。 如果标识符比单个字符长且以大写字符开头,编译器会尝试与标识符模式进行匹配。 此模式的标识符可以是用 Literal 属性标记的值、可区分联合用例、异常标识符或活动模式用例。 如果找不到匹配的标识符,则匹配失败,并将下一个模式规则(变量模式)与输入进行比较。

可区分联合模式可以是简单的命名用例,也可以有一个值,或者一个包含多个值的元组。 如果有值,则必须为该值指定标识符。 如果有元组,则必须提供元组模式,其中元组的每个元素都带有标识符,或者一个或多个命名联合字段的字段名称带有标识符。 有关示例,请参阅本部分中的代码示例。

option 类型是具有两个用例(SomeNone)的可区分联合。 一个用例 (Some) 有值,另一个用例 (None) 只是一个命名用例。 因此,对于与 Some 用例关联的值,Some 必须具有一个变量,而 None 必须单独出现。 以下代码将为变量 var1 提供通过匹配 Some 用例获得的值。

let printOption (data : int option) =
    match data with
    | Some var1  -> printfn "%d" var1
    | None -> ()

在以下示例中,PersonName 可区分联合包含表示可能的名称形式的字符串和字符组合。 可区分联合的用例为 FirstOnlyLastOnlyFirstLast

type PersonName =
    | FirstOnly of string
    | LastOnly of string
    | FirstLast of string * string

let constructQuery personName =
    match personName with
    | FirstOnly(firstName) -> printf "May I call you %s?" firstName
    | LastOnly(lastName) -> printf "Are you Mr. or Ms. %s?" lastName
    | FirstLast(firstName, lastName) -> printf "Are you %s %s?" firstName lastName

对于具有命名字段的可区分联合,可以使用等号 (=) 来提取命名字段的值。 以具有如下声明的可区分联合为例。

type Shape =
    | Rectangle of height : float * width : float
    | Circle of radius : float

可以在模式匹配表达式中使用命名字段,如下所示。

let matchShape shape =
    match shape with
    | Rectangle(height = h) -> printfn $"Rectangle with length %f{h}"
    | Circle(r) -> printfn $"Circle with radius %f{r}"

命名字段是可选的,因此在前面的示例中,Circle(r)Circle(radius = r) 具有相同的效果。

指定多个字段时,请使用分号 (;) 作为分隔符。

match shape with
| Rectangle(height = h; width = w) -> printfn $"Rectangle with height %f{h} and width %f{w}"
| _ -> ()

活动模式允许定义更复杂的自定义模式匹配。 有关活动模式的详细信息,请参阅活动模式

标识符是异常的用例用于异常处理程序上下文中的模式匹配。 有关异常处理中的模式匹配的信息,请参阅异常:try...with 表达式

变量模式

变量模式将匹配的值赋给变量名称,然后可在 -> 符号右侧的执行表达式中使用该名称。 变量模式单独匹配任何输入,但变量模式经常出现在其他模式中,因此可以将比较复杂的结构(如元组和数组)分解为变量。

以下示例演示了元组模式中的变量模式。

let function1 x =
    match x with
    | (var1, var2) when var1 > var2 -> printfn "%d is greater than %d" var1 var2
    | (var1, var2) when var1 < var2 -> printfn "%d is less than %d" var1 var2
    | (var1, var2) -> printfn "%d equals %d" var1 var2

function1 (1,2)
function1 (2, 1)
function1 (0, 0)

as 模式

as 模式是一种附加有 as 子句的模式。 as 子句将匹配的值绑定到可在 match 表达式的执行表达式中使用的名称,或者,如果在 let 绑定中使用此模式,则将该名称添加为本地范围绑定。

以下示例使用 as 模式。

let (var1, var2) as tuple1 = (1, 2)
printfn "%d %d %A" var1 var2 tuple1

OR 模式

如果输入数据可以匹配多个模式,并且你希望执行相同的代码作为结果,则使用 OR 模式。 OR 模式两侧的类型必须兼容。

以下示例演示了 OR 模式。

let detectZeroOR point =
    match point with
    | (0, 0) | (0, _) | (_, 0) -> printfn "Zero found."
    | _ -> printfn "Both nonzero."
detectZeroOR (0, 0)
detectZeroOR (1, 0)
detectZeroOR (0, 10)
detectZeroOR (10, 15)

AND 模式

AND 模式要求输入匹配两个模式。 AND 模式两侧的类型必须兼容。

以下示例类似于本主题后面的“元组模式”部分中所示的 detectZeroTuple,但此处使用 AND 模式获取 var1var2 作为值。

let detectZeroAND point =
    match point with
    | (0, 0) -> printfn "Both values zero."
    | (var1, var2) & (0, _) -> printfn "First value is 0 in (%d, %d)" var1 var2
    | (var1, var2)  & (_, 0) -> printfn "Second value is 0 in (%d, %d)" var1 var2
    | _ -> printfn "Both nonzero."
detectZeroAND (0, 0)
detectZeroAND (1, 0)
detectZeroAND (0, 10)
detectZeroAND (10, 15)

Cons 模式

cons 模式用于将列表分解为第一个元素 head 和包含其余元素的列表 tail。

let list1 = [ 1; 2; 3; 4 ]

// This example uses a cons pattern and a list pattern.
let rec printList l =
    match l with
    | head :: tail -> printf "%d " head; printList tail
    | [] -> printfn ""

printList list1

列表模式

列表模式允许将列表分解为多个元素。 列表模式本身只能匹配包含特定数量的元素的列表。

// This example uses a list pattern.
let listLength list =
    match list with
    | [] -> 0
    | [ _ ] -> 1
    | [ _; _ ] -> 2
    | [ _; _; _ ] -> 3
    | _ -> List.length list

printfn "%d" (listLength [ 1 ])
printfn "%d" (listLength [ 1; 1 ])
printfn "%d" (listLength [ 1; 1; 1; ])
printfn "%d" (listLength [ ] )

数组模式

数组模式类似于列表模式,可用于分解特定长度的数组。

// This example uses array patterns.
let vectorLength vec =
    match vec with
    | [| var1 |] -> var1
    | [| var1; var2 |] -> sqrt (var1*var1 + var2*var2)
    | [| var1; var2; var3 |] -> sqrt (var1*var1 + var2*var2 + var3*var3)
    | _ -> failwith (sprintf "vectorLength called with an unsupported array size of %d." (vec.Length))

printfn "%f" (vectorLength [| 1. |])
printfn "%f" (vectorLength [| 1.; 1. |])
printfn "%f" (vectorLength [| 1.; 1.; 1.; |])
printfn "%f" (vectorLength [| |] )

带圆括号模式

圆括号可以围绕模式分组,以实现所需的关联性。 在以下示例中,圆括号用于控制 AND 模式和 cons 模式之间的关联性。

let countValues list value =
    let rec checkList list acc =
       match list with
       | (elem1 & head) :: tail when elem1 = value -> checkList tail (acc + 1)
       | head :: tail -> checkList tail acc
       | [] -> acc
    checkList list 0

let result = countValues [ for x in -10..10 -> x*x - 4 ] 0
printfn "%d" result

元组模式

元组模式匹配元组形式的输入,并通过使用元组中每个位置的模式匹配变量将元组分解为其构成元素。

以下示例演示了元组模式,还使用了字面量模式、变量模式和通配符模式。

let detectZeroTuple point =
    match point with
    | (0, 0) -> printfn "Both values zero."
    | (0, var2) -> printfn "First value is 0 in (0, %d)" var2
    | (var1, 0) -> printfn "Second value is 0 in (%d, 0)" var1
    | _ -> printfn "Both nonzero."
detectZeroTuple (0, 0)
detectZeroTuple (1, 0)
detectZeroTuple (0, 10)
detectZeroTuple (10, 15)

记录模式

记录模式用于分解记录,以提取字段的值。 该模式不必引用记录的所有字段;任何省略的字段都不参与匹配,也不进行提取。

// This example uses a record pattern.

type MyRecord = { Name: string; ID: int }

let IsMatchByName record1 (name: string) =
    match record1 with
    | { MyRecord.Name = nameFound; MyRecord.ID = _; } when nameFound = name -> true
    | _ -> false

let recordX = { Name = "Parker"; ID = 10 }
let isMatched1 = IsMatchByName recordX "Parker"
let isMatched2 = IsMatchByName recordX "Hartono"

通配符模式

与变量模式一样,通配符模式由下划线 (_) 字符表示并匹配任何输入,不同的是,它会放弃输入,而不是将其分配给变量。 通配符模式通常在其他模式中用作 -> 符号右侧表达式中不需要的值的占位符。 通配符模式也经常用于模式列表的末尾,以匹配任何不匹配的输入。 本主题中的许多代码示例都演示了通配符模式。 前面的代码中就有一个示例。

具有类型注释的模式

模式可以具有类型注释。 其行为与其他类型注释类似,并像其他类型注释一样引导推理。 模式中的类型注释需要用圆括号括起来。 以下代码展示了具有类型注释的模式。

let detect1 x =
    match x with
    | 1 -> printfn "Found a 1!"
    | (var1 : int) -> printfn "%d" var1
detect1 0
detect1 1

类型测试模式

类型测试模式用于将输入与类型进行匹配。 如果输入类型与模式中指定的类型匹配(或是其派生类型),则匹配成功。

以下示例演示了类型测试模式。

open System.Windows.Forms

let RegisterControl(control:Control) =
    match control with
    | :? Button as button -> button.Text <- "Registered."
    | :? CheckBox as checkbox -> checkbox.Text <- "Registered."
    | _ -> ()

如果只检查标识符是否属于特定派生类型,则不需要该模式的 as identifier 部分,如以下示例所示:

type A() = class end
type B() = inherit A()
type C() = inherit A()

let m (a: A) =
    match a with
    | :? B -> printfn "It's a B"
    | :? C -> printfn "It's a C"
    | _ -> ()

Null 模式

当你使用允许 Null 值的类型时,Null 模式会匹配可能出现的 Null 值。 与 .NET Framework 代码进行互操作时,经常使用 Null 模式。 例如,.NET API 的返回值可能是 match 表达式的输入。 你可以根据返回值是否为 Null 以及返回值的其他特征来控制程序流。 你可以使用 Null 模式来防止 Null 值传播到程序的其余部分。

以下示例使用 Null 模式和变量模式。

let ReadFromFile (reader : System.IO.StreamReader) =
    match reader.ReadLine() with
    | null -> printfn "\n"; false
    | line -> printfn "%s" line; true

let fs = System.IO.File.Open("..\..\Program.fs", System.IO.FileMode.Open)
let sr = new System.IO.StreamReader(fs)
while ReadFromFile(sr) = true do ()
sr.Close()

Nameof 模式

当字符串的值等于 nameof 关键字后面的表达式时,nameof 模式与该字符串匹配。 例如:

let f (str: string) =
    match str with
    | nameof str -> "It's 'str'!"
    | _ -> "It is not 'str'!"

f "str" // matches
f "asdf" // does not match

有关可以取其名称的内容的信息,请参阅 nameof 运算符。

另请参阅