共用方式為


模式比對

模式是轉換輸入數據的規則。 它們會在整個 F# 中用來比較數據與邏輯結構、將數據分解成組成部分,或以各種方式從數據中擷取資訊。

備註

模式用於許多語言建構,例如 match 表示式。 當您在 let 系結、Lambda 表達式和與 try...with 表達式相關聯的異常處理器中處理函式的引數時,會使用這些引數。 如需詳細資訊,請參閱 比對表達式let 綁定Lambda 表達式:fun 關鍵詞例外:try...with 表達式

例如,在 match 表達式中,模式 是管道符號之後的內容。

match expression with
| pattern [ when condition ] -> result-expression
...

每個模式都會作為某種方式轉換輸入的規則。 在 match 表示式中,會接著檢查每個模式,以查看輸入數據是否與模式相容。 如果找到匹配項,結果表達式就會被執行。 如果找不到相符項目,則會測試下一個模式規則。 在 比對表示式中說明選擇性的 條件 部分。

下表顯示支援的模式。 在運行時間,輸入會根據數據表中所列的順序,針對下列每個模式進行測試,而且模式會以遞歸方式套用,從第一個套用到最後一個,因為它們出現在您的程式代碼中,然後從左到右針對每一行的模式套用。

名字 描述
常數模式 任何數值、字元或字串常值、列舉常數或定義的常值識別碼 1.0"test"30Color.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 類型是一個區分聯集,包含兩種情況: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 表示式

變數模式

變數模式會將比對的值指派給變數名稱,然後可用於 -> 符號右邊的執行表達式。 變數模式只會符合任何輸入,但變數模式通常會出現在其他模式內,因此可讓元組和數位等更複雜的結構分解成變數。

下列範例示範 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,但在這裡,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 模式可用來將清單分解成第一個元素、,以及包含其餘元素的清單,

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 運算符。

另請參閱