模式比對

模式是轉換輸入資料的規則。 它們在 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 型別為具有兩種案例的差異聯集,也就是 SomeNone。 一個案例 (Some) 具有值,但另一個 (None) 單純是具名案例。 因此,Some 必須具有與 Some 案例相關的值變數,但 None 必須單獨出現。 在下列程式碼中,變數 var1 會透過比對 Some 案例來取得值。

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

在下列範例中,PersonName 差異聯集包含了字串和字元的混合,而這些字串和字元代表可能的名稱形式。 差異聯集的案例為 FirstOnlyLastOnlyFirstLast

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,但在此處,var1var2 都是使用 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 運算子,以了解可以取得的名稱。

另請參閱