模式比對
模式是轉換輸入資料的規則。 它們在 F# 中可透過各種方式比較資料與邏輯結構、將資料分解成組成部分,或從資料中擷取資訊。
備註
模式可用於許多語言建構,例如 match
運算式。 當您處理 let
繫結、Lambda 運算式及 try...with
運算式相關的例外狀況處理常式中的函式引數時,會使用這些模式。 如需詳細資訊,請參閱比對運算式、let 繫結、Lambda 運算式:fun
關鍵字和例外狀況:try...with
運算式。
例如,在 match
運算式中,模式會出現在管道符號後。
match expression with
| pattern [ when condition ] -> result-expression
...
每個模式都可作為一種規則,以某種方式轉換輸入。 在 match
運算式中,系統會輪流檢查每個模式,以確定輸入資料是否與模式相容。 如果比對相符,則會執行結果運算式。 如果比對不符,則會測試下一個模式規則。 比對運算式中說明條件部分時,為選擇性。
支援的模式如下列表格所示。 在執行階段,輸入會根據資料表所列的順序,針對下列每個模式進行測試,而且模式會以遞迴方式依照它們出現在您的程式碼中的順序,從第一個到最後一個模式進行套用,並且針對每一行上的模式由左至右套用。
名稱 | 描述: | 範例 |
---|---|---|
常數模式 | 任何數值、字元或字串常值、列舉常數或定義的常值識別碼 | 1.0 , "test" , 30 , Color.Red |
識別碼模式 | 差異聯集、例外狀況標籤或作用中模式案例的案例值 | Some(x) Failure(msg) |
變數模式 | 識別碼 | a |
as 模式 |
作為識別碼的模式 | (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 運算式 | nameof str |
常數模式
常數模式是數值、字元和字串常值、列舉常數 (包含列舉型別名稱)。 只有常數模式的 match
運算式,可以與其他語言中的案例陳述式進行比較。 如果值相等,則輸入會與常值及相符項目進行比較。 常值的型別必須與輸入的型別相容。
下列範例示範常值模式的使用方式,另外也使用變數模式和 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
型別為具有兩種案例的差異聯集,也就是 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
針對有具名欄位的差異聯集,您可使用等號 (=) 來擷取具名欄位的值。 例如,您可考慮使用類似下列宣告的差異聯集。
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
,但在此處,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 模式可用於將清單分解成第一個元素,即 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
運算子,以了解可以取得的名稱。