模式匹配

模式是转换输入数据的规则。 它们在整个 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"30Color.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 模式 标识符 :: 列表标识符 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
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,但此处 var1var2 均使用 AND 模式获取为值。

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 模式用于将列表分解为第一个元素、,以及包含其余元素的列表、的列表。

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()

对于 F# 9 为 null 性功能,也建议使用 null 模式:

let len (str: string | null) =
    match str with
    | null -> -1
    | s -> s.Length

同样,可以使用与为 null 性相关的新的专用模式

let let str =       // str is inferred to be `string | null`
    match str with
    | Null -> -1
    | NonNull (s: string) -> s.Length

Nameof 模式

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 运算符。

另请参阅