模式比對 (F#)
「模式」(Pattern) 是轉換輸入資料的規則。在 F# 語言中模式用於比較具有邏輯結構的資料、將資料分解為構成部分,或是以各種方式從資料擷取資訊。
備註
模式可用於許多語言建構 (例如 match 運算式)。當您處理 let 繫結、Lambda 運算式,以及與 try...with 運算式相關聯之例外狀況處理常式中函式的引數時,都會使用模式。如需詳細資訊,請參閱搜尋運算式 (F#)、let 繫結 (F#)、Lambda 運算式:fun 關鍵字 (F#)和例外狀況:try...with 運算式 (F#)。
例如,在 match 運算式中,pattern 是位於管道符號後面的內容。
match expression with
| pattern [ when condition ] -> result-expression
...
每個模式都是一種轉換規則,以某種方式對輸入資料進行轉換。在 match 運算式中,會輪流檢查每個模式,確定輸入資料是否與模式相容。如果找到符合的項目,則會執行結果運算式。如果找不到符合的項目,則會測試下一個模式規則。搜尋運算式 (F#)會說明選擇性 when condition 這個部分。
下表說明支援的模式。在執行階段,會針對以下表格所列每個模式的順序測試輸入及遞迴套用模式。套用時,會從程式碼中第一個模式套用到最後一個模式,以及從左到右來套用每一行出現的模式。
名稱 |
描述 |
範例 |
---|---|---|
常數模式 |
任何數值、字元或字串常值、列舉常數或已定義的常值識別項 |
1.0, "test", 30, Color.Red |
識別項模式 |
已區分之聯集、例外狀況標籤或作用中模式案例的案例值 |
Some(x) Failure(msg) |
變數模式 |
identifier |
a |
as 模式 |
pattern as identifier |
(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 ) |
Tuple 模式 |
( 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 |
常數模式
常數模式是數值、字元和字串常值、列舉常數 (包含列舉型別名稱)。只有常數模式的 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
識別項模式
如果模式是形成有效識別碼的字元字串,則識別碼的形式決定模式的比對方式。如果識別項長度大於單一字元且開頭為大寫字元,編譯器會嘗試比對識別項模式。此模式的識別項可以是以常值屬性、差別聯集、例外狀況識別項或現用模式案例所標記的值。如果找不到符合的識別項,比對便失敗,下一個模式規則 (變數模式) 會與輸入比較。
已區分的聯集模式可以是簡單的具名案例、有單一值,或是包含多個值的 Tuple。如果有一個值,您必須指定值的識別項,在 Tuple 的情況下,則必須為 Tuple 的每個項目提供具有識別項的 Tuple 模式。如需範例,請參閱本節的程式碼範例。
option 型別是有 Some 和 None 兩個案例的已區分聯集。一個案例 (Some) 有值,但另一個 (None) 只是具名案例。因此,Some 必須有與 Some 案例關聯之值的變數,但是 None 必須單獨出現。在下列程式碼中,比對 Some 案例所取得的值會提供給變數 var1。
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#)。
變數模式
變數模式會將符合的值指派給變數名稱,接著此變數即可在 -> 符號右方的執行運算式中使用。變數模式本身會比對任何輸入,但是變數模式通常會出現在其他模式中,因此可將更複雜的結構 (例如 Tuple 和陣列) 分解為變數。
下列範例將示範 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 子句的模式。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 模式
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 "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 模式
Tuple 模式會比對 Tuple 形式的輸入,並且透過對 Tuple 中的每個位置使用模式比對變數,讓 Tuple 分解為其構成項目。
下列範例會示範 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."
| _ -> ()
Null 模式
null 模式會比對使用允許 null 值的型別時所出現的 null 值。Null 模式常用於 F# 與 .NET Framework 程式碼交互操作時。例如,.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()