Aktivní vzorky

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.

Syntaxe

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

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 |) se označují jako klipy banánů a funkce vytvořená tímto typem vazby let se nazývá aktivní rozpoznač.

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. Objekt může být například Color rozložen do reprezentace RGB nebo 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ř klipů banánů. 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 někdy jednotlivé volby oddělené nebo vzájemně se vylučují, ale nemusí být. V následujícím příkladu nejsou čtvercové vzory a vzorová krychle oddělené, protože některá čísla jsou čtverce i datové krychle, například 64. Následující program používá vzor AND ke kombinování vzorů čtverců a datových krychlí. Vytiskne všechna celá čísla až do 1 000, které jsou čtverce i datové krychle, stejně jako ty, které jsou pouze datové 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 odpovídající položce, ale můžou mít i další argumenty, v takovém případě se použije parametrizovaný aktivní vzor názvu. 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ý používá také čá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 porovnávání vzorů výrazů, můžete je také použít na vazbách 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

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

Částečné aktivní vzory ve výchozím nastavení vrací option hodnotu, která bude zahrnovat přidělení hodnoty pro Some úspěšnou shodu. Alternativně můžete jako návratovou hodnotu použít možnost hodnoty 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í vrácení struktury není odvozeno z jednoduché změny návratového typu na ValueOption. Další informace naleznete v dokumentu RFC FS-1039.

Viz také