模式匹配 (F#)

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

备注

许多语言构造(例如 match 表达式)中都使用模式。当您在 let 绑定、lambda 表达式和与 try...with 表达式关联的异常处理程序中处理函数的参数时,将会使用模式。有关更多信息,请参见 match 表达式 (F#)let 绑定 (F#)Lambda 表达式:fun 关键字 (F#)异常:try...with 表达式 (F#)

例如,在 match 表达式中,竖线后面就是 pattern。

match expression with

|pattern [ when condition ] -> result-expression

...

每个模式都充当采用某种方式转换输入的一条规则。在 match 表达式中,将依次检查每个模式,以确定输入数据是否与模式兼容。如果找到了匹配项,则执行结果表达式。如果未找到匹配项,则测试下一个模式规则。match 表达式 (F#)中解释了可选的“when condition”部分。

下表中显示了支持的模式。在运行时,将按表中所列的顺序依据以下每个模式对输入进行测试,并以递归方式应用模式,所遵循的应用顺序为:按模式在代码中显示的顺序从第一个到最后一个,并按模式在每一行上的顺序从左到右应用。

名称

说明

示例

常量模式

任何数值、字符或字符串文本、枚举常量或定义的文本标识符

1.0, "test", 30, Color.Red

标识符模式

可区分联合的用例值、异常标签或活动模式用例

Some(x)

Failure(msg)

变量模式

identifier

a

as 模式

模式 as 标识符

(a, b) as tuple1

OR 模式

模式 1 | 模式 2

([h] | [h; _])

AND 模式

模式 1 & 模式 2

(a, b) & (_, "test")

Cons 模式

identifier :: list-identifier

h :: t

列表模式

[ 模式 1; ...; 模式 n ]

[ a; b; c ]

数组模式

[| 模式 1; ..; 模式 n ]

[| a; b; c |]

带括号模式

( pattern )

( a )

元组模式

( 模式 1, ..., 模式 n )

( a, b )

记录模式

{ 标识符 1 = 模式 1; ...; 标识符 n = 模式 n }

{ Name = name; }

通配符模式

_

_

带有类型批注的模式

pattern : type

a : int

类型测试模式

:?type [ as identifier ]

:? System.DateTime as dt

Null 模式

null

null

常量模式

常量模式是数值、字符和字符串文本、枚举常量(包括枚举类型名称)。可将只有常量模式的 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

标识符模式

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

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

option 类型是具有两个用例(Some 和 None)的可区分联合。一个用例 (Some) 具有值,但另一个用例 (None) 只是命名用例。因此,Some 需要为与 Some 用例关联的值使用变量,但 None 必须单独出现。在下面的代码中,将为变量 var1 指定通过与 Some 用例匹配获得的值。

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

在下面的示例中,PersonName 可区分联合混合包含了用于表示可能的名称形式的字符串和字符。可分区联合的用例包括 FirstOnly、LastOnly 和 FirstLast。

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

利用活动模式可以定义更复杂的自定义模式匹配。有关活动模式的更多信息,请参见活动模式 (F#)

在异常处理程序上下文中的模式匹配内,将使用标识符为异常的用例。有关异常处理中的模式匹配的信息,请参见异常:try...with 表达式 (F#)

变量模式

变量模式将所匹配的值分配给变量名称,该名称随后可在 -> 符号右边的执行表达式中使用。变量模式单独使用时可匹配任何输入,但变量模式通常出现在其他模式内,从而能够将更复杂的结构(例如元组和数组)分解为变量。

下面的示例演示了一个元组模式内的变量模式。

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,但此处 var1 和 var2 都是通过使用 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 "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."
    | _ -> ()

Null 模式

Null 模式匹配您在使用允许 null 值的类型时可能出现的 null 值。在与 .NET Framework 代码进行互操作时,通常会使用 null 模式。例如,.NET API 的返回值可能是 match 表达式的输入。您可以根据返回值是否为空以及返回值的其他特性来控制程序流。可以使用 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()

请参见

参考

match 表达式 (F#)

活动模式 (F#)

其他资源

F# 语言参考