パターンは、入力データを変換するためのルールです。 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) |
| 変数パターン | 識別子 | a |
as パターン |
識別子としてのパターン | (a, b) as tuple1 |
| ORのパターン | pattern1 | pattern2 | ([h] | [h; _]) |
| AND パターン | pattern1 & pattern2 | (a, b) & (_, "test") |
| Cons パターン | 識別子 :: リスト識別子 | h :: t |
| リストのパターン | [ pattern_1; ... ; pattern_n ] | [ a; b; c ] |
| 配列パターン | [| pattern_1; ..; pattern_n |] | [| a; b; c |] |
| かっこで囲まれたパターン | ( パターン ) | ( 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
リテラル パターンのもう 1 つの例は、列挙定数に基づくパターンです。 列挙型定数を使用する場合は、列挙型名を指定する必要があります。
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
識別子パターン
パターンが有効な識別子を形成する文字の文字列である場合、識別子の形式によってパターンの照合方法が決まります。 識別子が 1 文字より長く、大文字で始まる場合、コンパイラは識別子パターンに一致しようとします。 このパターンの識別子は、Literal 属性が指定された値、判別共用体のケース、例外識別子、またはアクティブ パターンのケースです。 一致する識別子が見つからない場合、一致は失敗し、次のパターン ルールである変数パターンが入力と比較されます。
判別共用体パターンは、単純な名前付きケースであるか、値またはタプル (複数の値を含む) を含むかのいずれかです。 値がある場合は、値の識別子を指定する必要があります。 タプルの場合は、タプルの各要素の識別子を持つタプル パターン、または 1 つ以上の名前付き共用体フィールドのフィールド名を持つ識別子を指定する必要があります。 例については、このセクションのコード例を参照してください。
option 型は、Some と Noneの 2 つのケースを持つ判別共用体です。 1 つのケース (Some) には値がありますが、もう 1 つのケース (None) は単なる名前付きケースです。 そのため、SomeSome ケースに関連付けられた値の変数が必要ですが、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 式」を参照してください。
変数パターン
変数パターンは、一致する値を変数名に割り当てます。この値は、-> シンボルの右側にある実行式で使用できます。 変数パターンだけがすべての入力に一致しますが、変数パターンは他のパターン内に出現することが多いため、タプルや配列などのより複雑な構造を変数に分解できます。
次の例は、タプル パターン内の変数パターンを示しています。
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 に似ていますが、ここでは、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 パターンは、リストを最初の要素、ヘッド、残りの要素を含むリスト、末尾に分解するために使用されます。
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
複数の cons パターンを連結して、要素の特定のシーケンスで始まるリストに一致することもできます。
let charList = ['A'; 'B'; 'C'; 'D']
// This example demonstrates multiple cons patterns.
let matchChars xs =
match xs with
| 'A'::'B'::t -> printfn "starts with 'AB', rest: %A" t
| 'A'::t -> printfn "starts with 'A', rest: %A" t
| 'C'::'D'::t -> printfn "starts with 'CD', rest: %A" t
| _ -> printfn "does not match"
matchChars charList
matchChars ['A'; 'X']
matchChars ['C'; 'D'; 'E']
リスト パターン
リスト パターンを使用すると、リストを複数の要素に分解できます。 リスト パターン自体は、特定の数の要素のリストにのみ一致できます。
// 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"
ワイルドカード パターン
ワイルドカード パターンはアンダースコア (_) 文字で表され、変数パターンと同様に任意の入力と一致します。ただし、入力は変数に割り当てるのではなく破棄されます。 ワイルドカード パターンは、他のパターン内で、-> 記号の右側の式で必要のない値のプレースホルダーとして使用されることがよくあります。 ワイルドカード パターンは、一致しない入力に一致するパターンの一覧の末尾でも頻繁に使用されます。 ワイルドカード パターンは、このトピックの多くのコード例で示されています。 1 つの例については、上記のコードを参照してください。
次のコードは、ワイルドカード パターンの追加の使用方法を示しています。
// Wildcard pattern matching "nothing" examples
// Example 1: Wildcard ignoring function parameters
let ignoreAllParams _ _ = "Ignores all input"
// Example 2: Wildcard in destructuring, ignoring elements
let getFirstOnly (first, _) = first
// Example 3: Using wildcard to ignore optional values
let handleEmpty opt =
match opt with
| Some _ -> "Has something"
| None -> "Has nothing"
// Usage
printfn "%s" (ignoreAllParams 42 "test")
printfn "%d" (getFirstOnly (1, "ignored"))
printfn "%s" (handleEmpty None)
型の注釈が付けられたパターン
パターンには型注釈を含めることができます。 これらは他の型注釈と同様に動作し、他の型注釈と同様に推論をガイドします。 パターンの型注釈の周りには括弧が必要です。
型注釈を持つパターンは、構文 pattern : type を使用し、型チェッカーに コンパイル時の型情報 を提供します。 これは純粋に、型の推論に役立つ静的な型注釈です。実行時の型チェックや変換は実行されません。 コンパイラは、コンパイル時にこの情報を使用して、パターン変数の型を決定します。
次のコードは、型注釈を持つパターンを示しています。
let detect1 x =
match x with
| 1 -> printfn "Found a 1!"
| (var1 : int) -> printfn "%d" var1
detect1 0
detect1 1
この例では、 (var1 : int) は、 var1 が int型であることをコンパイラに通知します。 これはコンパイル時に解決され、生成されたコードは一致式全体で var1 を整数として扱います。 このパターンは任意の整数値と一致し、 var1にバインドされます。
主な特性:
- 構文
pattern : typeを使用します (コロンは 1 つ)。 - コンパイル時に解決 - 型チェッカーに型情報を提供します。
- ランタイム型テストは実行しません。
- 型の推論とコンパイラのガイドに使用されます。
型テスト パターン
型テスト パターンは、実行時に入力を型と照合するために使用 されます。 入力型がパターンで指定された型に一致する (または派生型の) 場合、一致は成功します。
型テスト パターンでは、構文:? typeを使用し、C# のis演算子やas演算子と同様にランタイム型チェックを実行します。 このパターンは、プログラムの実行中に値が特定の型であるかどうかをテストし、継承階層またはインターフェイス実装を操作するときに役立ちます。
次の例では、型テスト パターンを示します。
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"
| _ -> ()
主な特性:
- 構文
:? typeまたは:? type as identifier(疑問符付き) を使用します。 - 実行時に解決 - 実行中に実際の型チェックを実行します。
- 値が特定の型またはその派生型のインスタンスであるかどうかをテストします。
- 継承階層とポリモーフィック型でよく使用されます。
- C# の
is演算子またはas演算子に似ています。
型の注釈と型テスト パターンのコントラスト
どちらのパターンにも型が含まれますが、さまざまな目的に対応します。
| 特徴 | 型注釈パターン (pattern : type) |
型テスト パターン (:? type) |
|---|---|---|
| 構文 | 1 つのコロン: a : int |
疑問符付きのコロン: :? Button |
| 解決された場合 | コンパイル時間 | ランタイム |
| Purpose | ガイド型の推論 | 実際の値の種類をテストする |
| ユースケース | コンパイラが型を理解するのに役立つ | 継承階層でのランタイム型の確認 |
| C で同等# | スイッチ パターンで注釈を入力する |
is 演算子または as 演算子 |
次の例では、違いを示します。
// Type annotation pattern - compile time
let detect1 x =
match x with
| 1 -> printfn "Found a 1!"
| (var1 : int) -> printfn "%d" var1
// The ': int' tells the compiler var1 is an int
// Everything is resolved at compile time
// Type test pattern - runtime
type A() = class end
type B() = inherit A()
let test (a: A) =
match a with
| :? B -> printfn "Runtime check: it's a B"
| _ -> printfn "Runtime check: it's not a B"
// The ':? B' performs a runtime type check
// The actual type is tested during execution
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()
F# 9 null 許容機能には、null パターンも推奨されます。
let len (str: string | null) =
match str with
| null -> -1
| s -> s.Length
同様に、パターンに関連する新しい専用の null 許容を使用できます。
let len str = // str is inferred to be `string | null`
match str with
| Null -> -1
| NonNull (s: string) -> s.Length
Nameof パターン
nameof パターンは、その値が nameof キーワードに続く式と等しい場合に、文字列と一致します。 このパターンは、コード内の型、判別共用体のケース、またはその他のシンボルの名前と文字列値を照合する必要がある場合に特に便利です。 シンボルの名前を変更すると、パターンで自動的に新しい名前が使用されるため、 nameof を使用するとコンパイル時の安全性が確保されます。
一般的なユース ケースは、文字列値が型または大文字と小文字の名前を表すデータを逆シリアル化することです。
type EventType =
| OrderCreated
| OrderShipped
| OrderDelivered
let handleEvent eventName data =
match eventName with
| nameof OrderCreated -> printfn "Processing order creation: %s" data
| nameof OrderShipped -> printfn "Processing order shipment: %s" data
| nameof OrderDelivered -> printfn "Processing order delivery: %s" data
| _ -> printfn "Unknown event type: %s" eventName
handleEvent "OrderCreated" "Order #123" // matches first case
handleEvent "OrderShipped" "Order #123" // matches second case
この方法は、文字列リテラル ( "OrderCreated" など) を使用するよりも優れています。理由は次のとおりです。
-
OrderCreatedの名前をOrderPlacedに変更すると、パターンが自動的に更新されます。 - コンパイラはシンボルが存在することを保証し、入力ミスを防ぎます。
- リファクタリング時にコードの一貫性が維持されます。
パラメーターで 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 演算子を参照してください。
関連項目
.NET