模式比對
模式是轉換輸入數據的規則。 它們會在整個 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") |
缺點模式 | 識別碼 :: 清單識別碼 | h :: t |
列表樣式 | [ pattern_1; ... ; pattern_n ] | [ a; b; c ] |
陣列模式 | [| pattern_1; ..; pattern_n |] | [| a; b; c |] |
括弧模式 | (模式) | ( a ) |
Tuple 模式 | (pattern_1,...,pattern_n) | ( a, b ) |
記錄模式 | { identifier1 = pattern_1; ... ; identifier_n = pattern_n } | { Name = name; } |
通配符模式 | _ | _ |
模式與類型批注 | 模式:類型 | a : int |
類型測試圖樣 | :? 類型 [ 作為 識別子 ] | :? 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
類型是一個區分聯集,包含兩種情況: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
表示式。
變數模式
變數模式會將比對的值指派給變數名稱,然後可用於 ->
符號右邊的執行表達式。 變數模式只會符合任何輸入,但變數模式通常會出現在其他模式內,因此可讓元組和數位等更複雜的結構分解成變數。
下列範例示範 Tuple 模式內的變數模式。
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
子句會將匹配的值綁定到一個可在 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 模式的兩端類型必須相容。
下列範例就像本主題稍後的 Tuple 模式一節中顯示的 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 模式可用來將清單分解成第一個元素、頭,以及包含其餘元素的清單,尾。
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
Tuple 模式
元組模式會比對元組形式的輸入,並允許使用模式比對變數來將元組分解為其組成元素。
下列範例示範元組模式,並使用常值模式、變數模式和通配符模式。
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 模式
空值模式會匹配您在使用允許空值的類型時可能出現的空值。 與 .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 nullability 功能也建議使用 Null 模式:
let len (str: string | null) =
match str with
| null -> -1
| s -> s.Length
同樣地,您可以使用專門針對可空值的 模式:
let let str = // str is inferred to be `string | null`
match str with
| Null -> -1
| NonNull (s: string) -> s.Length
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
運算符。