Udostępnij za pośrednictwem


Aktywne wzorce

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

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 wiązania let jest nazywana aktywnym rozpoznawcą.

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 obiekt Color 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ęść wejściowego obszaru. 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ęściowe aktywne wzorce; 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)

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

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 nazwa parametryzowany aktywny wzorzec ma zastosowanie. 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 wiązaniach let-expressions.

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

Zwracany typ dla częściowo aktywnych wzorców

Częściowe aktywne wzorce zwracają Some (), aby wskazać dopasowanie, i None w przeciwnym przypadku.

Rozważ ten mecz:

match key with
| CaseInsensitive "foo" -> ...
| CaseInsensitive "bar" -> ...

Częściowy aktywny wzorzec dla niego to:

let (|CaseInsensitive|_|) (pattern: string) (value: string) =
    if String.Equals(value, pattern, StringComparison.OrdinalIgnoreCase) then
        Some ()
    else
        None

Począwszy od F# 9, takie wzorce mogą również zwracać bool:

let (|CaseInsensitive|_|) (pattern: string) (value: string) =
    String.Equals(value, pattern, StringComparison.OrdinalIgnoreCase)

Reprezentacje struktury dla częściowych aktywnych wzorców

Domyślnie, jeśli wzorzec częściowy aktywny zwraca option, wiąże się to z alokacją wartości Some w przypadku pomyślnego dopasowania. Aby tego uniknąć, 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 zwracania wartości typu struct nie jest wnioskowane z prostego zmienienia typu zwracanego na ValueOption. Aby uzyskać więcej informacji, zobacz RFC FS-1039.

Wzorce aktywne null

W języku F# 9 dodano aktywne wzorce związane z nullością.

Pierwszy z nich to | Null | NonNull x |, który jest zalecanym sposobem obsługi możliwych wartości pustych. W poniższym przykładzie parametr s jest wnioskowany jako nullowalny przez zastosowanie tego wzorca aktywnego.

 let len s =
    match s with
    | Null -> -1
    | NonNull s -> String.length s

Jeśli wolisz automatycznie rzucić NullReferenceException, możesz użyć wzorca | NonNullQuick |.

let len (NonNullQuick str) =  // throws if the argument is null
    String.length str

Zobacz też