模式匹配 (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# 语言参考