作用中的模式 (F#)
「作用中的模式」(Active Pattern) 可讓您定義細分輸入資料的具名分割,以便在模式比對運算式中使用這些名稱,就如同已區分的聯集一樣。您可以使用作用中的模式,以自訂方式分解每個分割的資料。
// Complete active pattern definition.
let (|identifer1|identifier2|...|) [ arguments ] = expression
// Partial active pattern definition.
let (|identifier|_|) [ arguments ] = expression
備註
在上述語法中,識別項是 arguments 所表示輸入資料的分割名稱,也就是所有引數值集合的子集名稱。在現用模式定義中最多可以有七個分割。expression 描述分解資料的形式。您可以使用作用中模式定義,來定義判斷所提供引數值屬於哪個具名分割的規則。(| 和 |) 符號稱為「香蕉夾」(Banana Clip),這類 let 繫結所建立的函式稱為「現用辨識器」(Active Recognizer)。
例如,請考量下列具有引數的作用中模式。
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
作用中模式的這兩種使用方式結合起來可讓您將資料分割及分解為適當形式,並且以最方便計算的形式在適當資料上執行適當計算。
結果模式比對運算式可讓資料方便撰寫,而且極為容易閱讀、大幅簡化原來可能是複雜的分支和資料分析程式碼。
部分作用中的模式
您有時只需要分割輸入空間的一部分。在此情況下,可以撰寫一組部分模式,其中每個模式符合某些輸入,但不符合其他輸入。不一定產生值的作用中模式稱為「部分作用中模式」(Partial Active Pattern);它們的傳回值為選項型別。若要定義部分作用中的模式,請在香蕉夾中的模式清單結尾使用萬用字元 (_)。在下列程式碼中,將示範部分作用中模式的用法。
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。下列程式會列出在 1000000 以前同時是平方數和立方數的所有整數。
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 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
參數化的作用中模式
作用中的模式一定至少接受一個表示比對項目的引數,但也可以接受其他引數,在此情況下適用名稱「參數化的作用中模式」(Parameterized Active Pattern)。其他引數允許特殊化一般模式。例如,使用規則運算式剖析字串的作用中模式通常包含規則運算式做為額外的參數,如下列程式碼中,也會使用上述程式碼範例中定義的部分作用中模式 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