Sdílet prostřednictvím


Aktivní vzory

aktivní vzory umožňují definovat pojmenované oddíly, které rozdělují vstupní data, abyste je mohli použít ve vzorovém shodném výrazu stejně jako u diskriminovaného sjednocení. Aktivní vzory můžete použít k dekompilování dat přizpůsobeným způsobem pro každý oddíl.

Syntax

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

Poznámky

V předchozí syntaxi jsou identifikátory názvy oddílů vstupních dat, které jsou reprezentovány argumenty, nebo jinými slovy názvy podmnožiny sady všech hodnot argumentů. V definici aktivního vzoru může existovat až sedm oddílů. Výraz popisuje formulář, do kterého se mají data rozložit. Pomocí definice aktivního vzoru můžete definovat pravidla pro určení, do kterých pojmenovaných oddílů hodnoty zadané jako argumenty patří. Symboly (| a |) jsou označovány jako banánové sponky a funkce vytvořená tímto typem let vazby se nazývá aktivní rozpoznávač.

Jako příklad zvažte následující aktivní vzor s argumentem.

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

Aktivní vzor můžete použít ve výrazu porovnávání vzorů, jak je znázorněno v následujícím příkladu.

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

TestNumber 7
TestNumber 11
TestNumber 32

Výstup tohoto programu je následující:

7 is odd
11 is odd
32 is even

Dalším použitím aktivních vzorů je rozložit datové typy několika způsoby, například když mají stejná podkladová data různé možné reprezentace. Například objekt Color lze rozdělit do reprezentace RGB nebo do reprezentace 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"

Výstup výše uvedeného programu je následující:

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

V kombinaci tyto dva způsoby použití aktivních vzorů umožňují rozdělit a rozložit data pouze do vhodného formuláře a provést příslušné výpočty s příslušnými daty ve formuláři, který je pro výpočet nejvhodnější.

Výsledné výrazy porovnávání vzorů umožňují zápis dat pohodlným způsobem, který je velmi čitelný, což výrazně zjednodušuje potenciálně složité větvení a kód analýzy dat.

Částečné aktivní vzory

Někdy potřebujete rozdělit jenom část vstupního prostoru. V takovém případě napíšete sadu částečných vzorů, z nichž každá odpovídá některým vstupům, ale neshoduje se s jinými vstupy. Aktivní vzory, které ne vždy vytvářejí hodnotu, se nazývají částečné aktivní vzory; mají návratovou hodnotu, která je typem možnosti. K definování částečného aktivního vzoru použijete zástupný znak (_) na konci seznamu vzorů uvnitř 'banana clips'. Následující kód znázorňuje použití částečného aktivního vzoru.

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"

Výstup předchozího příkladu je následující:

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

Při použití částečných aktivních vzorů mohou být jednotlivé volby někdy oddělené nebo se vzájemně vylučují, ale nemusí tomu tak být. V následujícím příkladu nejsou vzory čtverců a vzory krychlí nesouvislé, protože některá čísla jsou čtverce i krychle, například 64. Následující program používá vzor AND ke kombinování vzorů čtverců a datových krychlí. Vypíše všechna celá čísla až do 1 000, která jsou čtverce i krychle, stejně jako ta, která jsou pouze krychle.

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)

Výstup je následující:

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

Parametrizované aktivní vzory

Aktivní vzory vždy přebírají alespoň jeden argument pro odpovídající položku, ale můžou mít i další argumenty. V takovém případě platí název parametrizovaný aktivní vzor. Další argumenty umožňují, aby byl obecný vzor specializovaný. Například aktivní vzory, které používají regulární výrazy k analýze řetězců, často zahrnují regulární výraz jako dodatečný parametr, stejně jako v následujícím kódu, který také používá částečný aktivní vzor Integer definovaný v předchozím příkladu kódu. V tomto příkladu jsou řetězce, které používají regulární výrazy pro různé formáty kalendářních dat, dány k přizpůsobení obecného aktivního vzoru ParseRegex. Aktivní vzor Integer slouží k převodu odpovídajících řetězců na celá čísla, která lze předat konstruktoru 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())

Výstup předchozího kódu je následující:

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

Aktivní vzory nejsou omezeny pouze na výrazy pro porovnávání vzorů, můžete je také použít na přiřazení pomocí 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")

Výstup předchozího kódu je následující:

Hello, random citizen!
Hello, George!

Všimněte si však, že parametrizovat lze pouze jednoúčelové aktivní vzory.

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

Návratový typ pro částečné aktivní vzory

Částečné aktivní vzory vracejí Some () pro označení shody a None jinak.

Zvažte tuto shodu:

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

Částečný aktivní vzor by byl následující:

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

Počínaje verzí F# 9 mohou tyto vzory také vracet bool:

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

Reprezentace struktur pro částečné aktivní vzory

Ve výchozím nastavení, pokud částečný aktivní vzor vrátí option, bude to zahrnovat přidělení hodnoty Some, pokud dojde k úspěšné shodě. Pokud se tomu chcete vyhnout, můžete použít možnost hodnoty jako návratovou hodnotu pomocí atributu Struct:

open System

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

Atribut musí být zadán, protože použití návratové hodnoty struktury nelze odvodit z pouhé změny návratového typu na ValueOption. Další informace viz RFC FS-1039.

Viz také