パターン マッチ

パターンは、入力データの変換規則です。 データを論理構造と比較したり、データを構成要素に分解したり、さまざまな方法でデータから情報を抽出したりするために、F# 言語全体で使用されます。

解説

パターンは、match 式などの多くの言語構成要素で使用されます。 let 束縛、ラムダ式、および try...with 式に関連付けられている例外ハンドラーで関数の引数を処理する場合に使用されます。 詳細については、「match 式」、「let 束縛」、「ラムダ式: fun キーワード」、および「例外: try...with」を参照してください。

たとえば、match 式では、パターン がパイプ記号の後に続きます。

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

各パターンは、なんらかの方法で入力を変換する際の規則として機能します。 match 式では、各パターンが順に調べられ、入力データにパターンとの互換性があるかどうかが確認されます。 一致が見つかった場合は、結果の式が実行されます。 一致が見つからなかった場合は、次のパターン規則がテストされます。 condition 部分が match 式で説明されている場合は、省略可能です。

サポートされているパターンを次の表に示します。 実行時に、表に示されている順序で次の各パターンに対して入力がテストされます。パターンは、コードに示されているとおりに先頭から末尾へ、各行のパターンの左から右へ、再帰的に適用されます。

名前 説明
定数パターン 数値、文字、リテラル文字列、列挙定数、または定義済みのリテラル識別子 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 )
タプル パターン ( 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 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

識別子パターン

パターンが有効な識別子になる文字列の場合は、識別子の形式でパターンの照合方法が決まります。 識別子が 2 文字以上で、大文字で始まる場合は、コンパイラが識別子パターンとの照合を試みます。 このパターンの識別子は、Literal 属性が指定された値、判別共用体のケース、例外識別子、またはアクティブ パターンのケースです。 一致する識別子が見つからない場合は、照合が失敗し、次のパターン規則の変数パターンが入力と比較されます。

判別共用体パターンは、単純な名前付きケースであるか、値またはタプル (複数の値を含む) を含むかのいずれかです。 値が存在する場合は、値の識別子を指定する必要があります。 タプルの場合は、タプルの各要素の識別子、または 1 つ以上の名前付きの共用体フィールドのフィールド名の識別子、を持つタプル パターンを指定する必要があります。 例については、このセクションのコード例を参照してください。

option 型は、SomeNone の 2 つのケースを持つ判別共用体です。 一方のケース (Some) には値が含まれますが、もう一方のケース (None) は単なる名前付きケースです。 したがって、Some には、Some ケースに関連付けられた値の変数が必要ですが、None は単体で使用する必要があります。 次のコードでは、var1 変数に、Some ケースとの照合で取得された値が指定されます。

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

次の例では、PersonName 判別共用体に、名前に使用できる形式を表す文字列および文字が混在しています。 判別共用体のケースは FirstOnlyLastOnly、および 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 パターンでは、入力が 2 つのパターンと一致する必要があります。 AND パターンの両側の型に互換性があることが必要です。

次の例は、このトピック内で後述の「タプル パターン」に示す detectZeroTuple に似ていますが、ここでは、AND パターンを使用して、var1var2 の両方を値として取得しています。

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 値は、null 値を許可する型を使用しているときに示される可能性があります。 null パターンは、多くの場合、.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()

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」演算子を参照してください。

関連項目