アクティブ パターン (F#)
アクティブなパターンでは、入力データを分割する名前付きパーティションを定義できます。これによって、判別共用体の場合と同様に、パターン一致式でそれらの名前を使用できます。 アクティブなパターンを使用すると、パーティションごとにカスタマイズした方法でデータを分解できます。
// Complete active pattern definition.
let (|identifer1|identifier2|...|) [ arguments ] = expression
// Partial active pattern definition.
let (|identifier1|identifier2|...|_|) [ arguments ] = expression
解説
前の構文の identifier は、arguments で表される入力データのパーティションの名前、つまり引数のすべての値のセットのサブセットの名前です。 アクティブ パターンの定義に含めることができるパーティションの数は最大 7 つです。 expression には、データを分解する形式を記述します。 アクティブなパターンの定義を使用すると、引数として指定した値が属する名前付きパーティションを決定するための規則を定義できます。 (| および |) の記号をバナナ クリップと呼び、この種類の 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
また、アクティブなパターンを使用すると、基になるデータは同じでも表現が異なる場合などに、複数の方法でデータ型を分解することができます。 たとえば、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
R: 255 G: 0 B: 0
H: 0.000000 S: 1.000000 B: 0.500000
Black
R: 0 G: 0 B: 0
H: 0.000000 S: 0.000000 B: 0.000000
White
R: 255 G: 255 B: 255
H: 0.000000 S: 0.000000 B: 1.000000
Gray
R: 128 G: 128 B: 128
H: 0.000000 S: 0.000000 B: 0.501961
BlanchedAlmond
R: 255 G: 235 B: 205
H: 36.000000 S: 1.000000 B: 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.
部分的なアクティブなパターンを使用する場合は、それぞれの選択が非結合になる、つまり互いに重ならないことがありますが、必ずしもそうである必要はありません。 たとえば、次の例のパターン Square とパターン Cube は非結合にはなっていません。一部の値は、64 のように、2 乗と 3 乗の両方であるためです。 次のプログラムでは、2 乗と 3 乗の両方である 1000000 までのすべての整数が出力されます。
let err = 1.e-10
let floatequal x y =
abs (x - y) < err
let (|Square|_|) (x : int) =
if floatequal (sqrt (float x)) (round (sqrt (float x))) then Some(x)
else None
let (|Cube|_|) (x : int) =
if floatequal ((float x) ** ( 1.0 / 3.0)) (round ((float x) ** (1.0 / 3.0))) then Some(x)
else None
let examineNumber x =
match x with
| Cube x -> printfn "%d is a cube" x
| _ -> ()
match x with
| Square x -> printfn "%d is a square" x
| _ -> ()
let findSquareCubes x =
if (match x with
| Cube x -> true
| _ -> false
&&
match x with
| Square x -> true
| _ -> false
)
then printf "%d \n" x
[ 1 .. 1000000 ] |> List.iter (fun elem -> findSquareCubes elem)
出力は次のとおりです。
1
64
729
4096
15625
46656
117649
262144
531441
1000000
パラメーター化されたアクティブなパターン
アクティブなパターンは、照合する項目の引数を常に、少なくとも 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