Wzorce aktywne

Aktywne wzorce umożliwiają definiowanie nazwanych partycji, które dzielą dane wejściowe, dzięki czemu można używać tych nazw w wyrażeniu pasującym do wzorca tak samo jak w przypadku unii dyskryminowanej. Aktywne wzorce umożliwiają rozłożenie danych w dostosowany sposób dla każdej partycji.

Składnia

// 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.
// Uses a FSharp.Core.option<_> to represent if the type is satisfied at the call site.
let (|identifier|_|) [arguments] valueToMatch = expression

Uwagi

W poprzedniej składni identyfikatory są nazwami partycji danych wejściowych reprezentowanych przez argumenty lub, innymi słowy, nazwy podzbiorów zestawu wszystkich wartości argumentów. W aktywnej definicji wzorca może znajdować się maksymalnie siedem partycji. Wyrażenie opisuje formularz, w którym mają być rozłożone dane. Możesz użyć aktywnej definicji wzorca, aby zdefiniować reguły określania, które z nazwanych partycji mają wartości podane jako argumenty. Symbole (| i |) są określane jako klipy bananowe, a funkcja utworzona przez ten typ powiązania let jest nazywana aktywnym rozpoznawaniem.

Rozważmy na przykład następujący aktywny wzorzec z argumentem.

let (|Even|Odd|) input = if input % 2 = 0 then Even else Odd

Aktywny wzorzec można użyć w wyrażeniu pasującym do wzorca, jak w poniższym przykładzie.

let TestNumber input =
   match input with
   | Even -> printfn "%d is even" input
   | Odd -> printfn "%d is odd" input

TestNumber 7
TestNumber 11
TestNumber 32

Dane wyjściowe tego programu są następujące:

7 is odd
11 is odd
32 is even

Innym zastosowaniem aktywnych wzorców jest rozłożenie typów danych na wiele sposobów, na przykład gdy te same dane bazowe mają różne możliwe reprezentacje. Na przykład Color obiekt można podzielić na reprezentację RGB lub reprezentację 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"

Dane wyjściowe powyższego programu są następujące:

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

W połączeniu te dwa sposoby używania aktywnych wzorców umożliwiają partycjonowanie i rozkładanie danych w odpowiednim formularzu i wykonywanie odpowiednich obliczeń na odpowiednich danych w formie najwygodniejszej dla obliczeń.

Wynikowe wyrażenia dopasowania wzorca umożliwiają pisanie danych w wygodny sposób, który jest bardzo czytelny, co znacznie upraszcza potencjalnie złożone rozgałęzianie i kod analizy danych.

Częściowe aktywne wzorce

Czasami należy podzielić tylko część miejsca wejściowego na partycje. W takim przypadku napiszesz zestaw częściowych wzorców, z których każdy jest zgodny z niektórymi danymi wejściowymi, ale nie pasuje do innych danych wejściowych. Aktywne wzorce, które nie zawsze generują wartość, są nazywane częściowo aktywnymi wzorcami; mają wartość zwracaną, która jest typem opcji. Aby zdefiniować częściowo aktywny wzorzec, należy użyć symbolu wieloznakowego (_) na końcu listy wzorców wewnątrz klipów bananowych. Poniższy kod ilustruje użycie częściowego aktywnego wzorca.

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"

Dane wyjściowe poprzedniego przykładu są następujące:

1.100000 : Floating point
0 : Integer
0.000000 : Floating point
10 : Integer
Something else : Not matched.

W przypadku używania częściowo aktywnych wzorców czasami poszczególne wybory mogą być rozłączne lub wzajemnie wykluczające się, ale nie muszą być. W poniższym przykładzie wzorzec Square i wzorzec Cube nie są rozłączne, ponieważ niektóre liczby są zarówno kwadratami, jak i modułami, takimi jak 64. Poniższy program używa wzorca AND do łączenia wzorców kwadratu i modułu. Wyświetla wszystkie liczby całkowite do 1000, które są zarówno kwadratami, jak i modułami, a także tymi, które są tylko modułami.

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)

Wynik jest następujący:

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

Sparametryzowane aktywne wzorce

Aktywne wzorce zawsze przyjmują co najmniej jeden argument dla dopasowanego elementu, ale mogą również przyjmować dodatkowe argumenty, w tym przypadku ma zastosowanie nazwa sparametryzowany aktywny wzorzec . Dodatkowe argumenty umożliwiają wyspecjalizowanie ogólnego wzorca. Na przykład aktywne wzorce, które używają wyrażeń regularnych do analizowania ciągów, często zawierają wyrażenie regularne jako dodatkowy parametr, jak w poniższym kodzie, który również używa częściowego aktywnego wzorca Integer zdefiniowanego w poprzednim przykładzie kodu. W tym przykładzie ciągi używające wyrażeń regularnych dla różnych formatów dat są podawane w celu dostosowania ogólnego wzorca ParseRegex aktywnego. Aktywny wzorzec liczby całkowitej służy do konwertowania pasujących ciągów na liczby całkowite, które można przekazać do konstruktora 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())

Dane wyjściowe poprzedniego kodu są następujące:

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

Aktywne wzorce nie są ograniczone tylko do wyrażeń dopasowywania wzorców. Można ich również używać w powiązaniach 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")

Dane wyjściowe poprzedniego kodu są następujące:

Hello, random citizen!
Hello, George!

Należy jednak pamiętać, że można sparametryzować tylko pojedyncze wzorce aktywne.

// 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

Reprezentacje struktury dla częściowych aktywnych wzorców

Domyślnie częściowe aktywne wzorce zwracają wartość, która będzie obejmować alokację option dla Some wartości w przypadku pomyślnego dopasowania. Alternatywnie możesz użyć opcji wartości jako wartości zwracanej za pomocą atrybutu Struct :

open System

[<return: Struct>]
let (|Int|_|) str =
   match Int32.TryParse(str) with
   | (true, n) -> ValueSome n
   | _ -> ValueNone

Należy określić atrybut, ponieważ użycie zwracanej struktury nie jest wnioskowane z prostego zmiany typu powrotu na ValueOption. Aby uzyskać więcej informacji, zobacz RFC FS-1039.

Zobacz też