アクティブ パターン を使用すると、入力データを分割する名前付きパーティションを定義できるため、判別共用体の場合と同様に、パターン マッチング式でこれらの名前を使用できます。 アクティブ パターンを使用して、パーティションごとにカスタマイズされた方法でデータを分解できます。
構文
// Active pattern of one choice.
let (|identifier|) [arguments] valueToMatch = expression
// Active Pattern with multiple choices.
// Uses a FSharp.Core.Choice<_,...,_> based on the number of case names. In F#, the limitation n <= 7 applies.
let (|identifier1|identifier2|...|) valueToMatch = expression
// Partial active pattern definition.
// Can use FSharp.Core.option<_>, FSharp.Core.voption<_> or bool to represent if the type is satisfied at the call site.
let (|identifier|_|) [arguments] valueToMatch = expression
注釈
前の構文では、識別子は 、引数で表される入力データのパーティションの名前、つまり、引数のすべての値のセットのサブセットの名前です。 アクティブなパターン定義には、最大 7 つのパーティションを含めることができます。 この式は、データを分解する形式を表します。 アクティブなパターン定義を使用して、引数として指定された値が属する名前付きパーティションを決定するための規則を定義できます。 (| および |) 記号は バナナ クリップ と呼ばれ、この種類の let バインディングによって作成された関数は アクティブな認識エンジンと呼ばれます。
例として、引数を含む次のアクティブ パターンを考えてみましょう。
let (|Even|Odd|) input = if input % 2 = 0 then Even else Odd
次の例のように、パターン マッチング式でアクティブ パターンを使用できます。
let TestNumber input =
match input with
| Even -> printfn "%d is even" input
| Odd -> printfn "%d is odd" input
TestNumber 7
TestNumber 11
TestNumber 32
このプログラムの出力は次のとおりです。
7 is odd
11 is odd
32 is even
アクティブ パターンのもう 1 つの用途は、同じ基になるデータにさまざまな表現がある場合など、複数の方法でデータ型を分解することです。 たとえば、 Color
オブジェクトを RGB 表現または HSB 表現に分解できます。
open System.Drawing
let (|RGB|) (col : System.Drawing.Color) =
( col.R, col.G, col.B )
let (|HSB|) (col : System.Drawing.Color) =
( col.GetHue(), col.GetSaturation(), col.GetBrightness() )
let printRGB (col: System.Drawing.Color) =
match col with
| RGB(r, g, b) -> printfn " Red: %d Green: %d Blue: %d" r g b
let printHSB (col: System.Drawing.Color) =
match col with
| HSB(h, s, b) -> printfn " Hue: %f Saturation: %f Brightness: %f" h s b
let printAll col colorString =
printfn "%s" colorString
printRGB col
printHSB col
printAll Color.Red "Red"
printAll Color.Black "Black"
printAll Color.White "White"
printAll Color.Gray "Gray"
printAll Color.BlanchedAlmond "BlanchedAlmond"
上記のプログラムの出力は次のとおりです。
Red
Red: 255 Green: 0 Blue: 0
Hue: 360.000000 Saturation: 1.000000 Brightness: 0.500000
Black
Red: 0 Green: 0 Blue: 0
Hue: 0.000000 Saturation: 0.000000 Brightness: 0.000000
White
Red: 255 Green: 255 Blue: 255
Hue: 0.000000 Saturation: 0.000000 Brightness: 1.000000
Gray
Red: 128 Green: 128 Blue: 128
Hue: 0.000000 Saturation: 0.000000 Brightness: 0.501961
BlanchedAlmond
Red: 255 Green: 235 Blue: 205
Hue: 36.000000 Saturation: 1.000000 Brightness: 0.901961
これら 2 つの方法を組み合わせてアクティブ パターンを使用すると、データをパーティション分割して適切な形式に分解し、計算に最も便利な形式で適切なデータに対して適切な計算を実行できます。
結果として得られるパターン マッチング式を使用すると、非常に読みやすい便利な方法でデータを書き込むことが可能になり、潜在的に複雑な分岐とデータ分析のコードが大幅に簡略化されます。
部分アクティブ パターン
場合によっては、入力領域の一部のみをパーティション分割する必要があります。 その場合は、一部の入力に一致するが、他の入力と一致しない部分パターンのセットを記述します。 常に値を生成しないアクティブ パターンは、 部分アクティブ パターンと呼ばれます。オプション型の戻り値があります。 部分的にアクティブなパターンを定義するには、バナナ クリップ内のパターンの一覧の末尾にワイルドカード文字 (_) を使用します。 次のコードは、部分的なアクティブ パターンの使用を示しています。
let (|Integer|_|) (str: string) =
let mutable intvalue = 0
if System.Int32.TryParse(str, &intvalue) then Some(intvalue)
else None
let (|Float|_|) (str: string) =
let mutable floatvalue = 0.0
if System.Double.TryParse(str, &floatvalue) then Some(floatvalue)
else None
let parseNumeric str =
match str with
| Integer i -> printfn "%d : Integer" i
| Float f -> printfn "%f : Floating point" f
| _ -> printfn "%s : Not matched." str
parseNumeric "1.1"
parseNumeric "0"
parseNumeric "0.0"
parseNumeric "10"
parseNumeric "Something else"
前の例の出力は次のとおりです。
1.100000 : Floating point
0 : Integer
0.000000 : Floating point
10 : Integer
Something else : Not matched.
部分的にアクティブなパターンを使用する場合、個々の選択肢が不整合であるか、相互に排他的である場合がありますが、その必要はありません。 次の例では、一部の数値は 64 などの正方形とキューブの両方であるため、パターン Square とパターン Cube は不整合ではありません。 次のプログラムでは、AND パターンを使用して、Square パターンと Cube パターンを組み合わせます。 四角形とキューブの両方である最大 1,000 個の整数と、キューブのみの整数がすべて出力されます。
let err = 1.e-10
let isNearlyIntegral (x:float) = abs (x - round(x)) < err
let (|Square|_|) (x : int) =
if isNearlyIntegral (sqrt (float x)) then Some(x)
else None
let (|Cube|_|) (x : int) =
if isNearlyIntegral ((float x) ** ( 1.0 / 3.0)) then Some(x)
else None
let findSquareCubes x =
match x with
| Cube x & Square _ -> printfn "%d is a cube and a square" x
| Cube x -> printfn "%d is a cube" x
| _ -> ()
[ 1 .. 1000 ] |> List.iter (fun elem -> findSquareCubes elem)
出力は次のとおりです。
1 is a cube and a square
8 is a cube
27 is a cube
64 is a cube and a square
125 is a cube
216 is a cube
343 is a cube
512 is a cube
729 is a cube and a square
1000 is a cube
パラメーター化されたアクティブ パターン
アクティブ パターンは、一致する項目に対して常に少なくとも 1 つの引数を受け取りますが、追加の引数を受け取る場合もあります。この場合、 名前パラメーター化されたアクティブ パターン が適用されます。 追加の引数を使用すると、一般的なパターンを特殊化できます。 たとえば、正規表現を使用して文字列を解析するアクティブ パターンには、多くの場合、次のコードのように正規表現が追加のパラメーターとして含まれます。これは、前のコード例で定義した部分アクティブ パターン Integer
も使用します。 この例では、さまざまな日付形式に正規表現を使用する文字列を指定して、一般的な ParseRegex アクティブ パターンをカスタマイズします。 Integer アクティブ パターンは、一致した文字列を DateTime コンストラクターに渡すことができる整数に変換するために使用されます。
open System.Text.RegularExpressions
// ParseRegex parses a regular expression and returns a list of the strings that match each group in
// the regular expression.
// List.tail is called to eliminate the first element in the list, which is the full matched expression,
// since only the matches for each group are wanted.
let (|ParseRegex|_|) regex str =
let m = Regex(regex).Match(str)
if m.Success
then Some (List.tail [ for x in m.Groups -> x.Value ])
else None
// Three different date formats are demonstrated here. The first matches two-
// digit dates and the second matches full dates. This code assumes that if a two-digit
// date is provided, it is an abbreviation, not a year in the first century.
let parseDate str =
match str with
| ParseRegex "(\d{1,2})/(\d{1,2})/(\d{1,2})$" [Integer m; Integer d; Integer y]
-> new System.DateTime(y + 2000, m, d)
| ParseRegex "(\d{1,2})/(\d{1,2})/(\d{3,4})" [Integer m; Integer d; Integer y]
-> new System.DateTime(y, m, d)
| ParseRegex "(\d{1,4})-(\d{1,2})-(\d{1,2})" [Integer y; Integer m; Integer d]
-> new System.DateTime(y, m, d)
| _ -> new System.DateTime()
let dt1 = parseDate "12/22/08"
let dt2 = parseDate "1/1/2009"
let dt3 = parseDate "2008-1-15"
let dt4 = parseDate "1995-12-28"
printfn "%s %s %s %s" (dt1.ToString()) (dt2.ToString()) (dt3.ToString()) (dt4.ToString())
前のコードの出力は次のとおりです。
12/22/2008 12:00:00 AM 1/1/2009 12:00:00 AM 1/15/2008 12:00:00 AM 12/28/1995 12:00:00 AM
アクティブ パターンはパターン マッチング式のみに限定されるのではなく、let バインディングでも使用できます。
let (|Default|) onNone value =
match value with
| None -> onNone
| Some e -> e
let greet (Default "random citizen" name) =
printfn "Hello, %s!" name
greet None
greet (Some "George")
前のコードの出力は次のとおりです。
Hello, random citizen!
Hello, George!
ただし、パラメーター化できるのは単一ケースのアクティブ パターンのみであることに注意してください。
// A single-case partial active pattern can be parameterized
let (| Foo|_|) s x = if x = s then Some Foo else None
// A multi-case active patterns cannot be parameterized
// let (| Even|Odd|Special |) (s: int) (x: int) = if x = s then Special elif x % 2 = 0 then Even else Odd
部分アクティブ パターンの戻り値の型
部分的にアクティブなパターンは、一致を示す Some ()
を返し、それ以外の場合 None
返します。
次の一致を検討してください。
match key with
| CaseInsensitive "foo" -> ...
| CaseInsensitive "bar" -> ...
部分的にアクティブなパターンは次のようになります。
let (|CaseInsensitive|_|) (pattern: string) (value: string) =
if String.Equals(value, pattern, StringComparison.OrdinalIgnoreCase) then
Some ()
else
None
F# 9 以降では、このようなパターンは bool
を返すこともできます。
let (|CaseInsensitive|_|) (pattern: string) (value: string) =
String.Equals(value, pattern, StringComparison.OrdinalIgnoreCase)
部分アクティブ パターンの構造体表現
既定では、部分的なアクティブ パターンが option
を返す場合、一致が成功した場合に Some
値の割り当てが行われます。 これを回避するには、Struct
属性を使用して戻り値として value オプションを使用します。
open System
[<return: Struct>]
let (|Int|_|) str =
match Int32.TryParse(str) with
| (true, n) -> ValueSome n
| _ -> ValueNone
構造体の戻り値の使用は、単に戻り値の型を ValueOption
に変更するだけでは推論されないため、属性を指定する必要があります。 詳細については、 RFC FS-1039 を参照してください。
Null アクティブ パターン
F# 9 では、null 許容関連のアクティブ パターンが追加されました。
最初の方法は | Null | NonNull x |
です。これは、可能な null を処理するための推奨される方法です。 次の例では、パラメーター s
は、このアクティブなパターンの使用によって null 許容として推論されます。
let len s =
match s with
| Null -> -1
| NonNull s -> String.length s
NullReferenceException
を自動的にスローする場合は、| NonNullQuick |
パターンを使用できます。
let len (NonNullQuick str) = // throws if the argument is null
String.length str
こちらも参照ください
.NET