模式匹配 (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()