Aktiva mönster

Med aktiva mönster kan du definiera namngivna partitioner som delar upp indata, så att du kan använda dessa namn i ett mönstermatchningsuttryck precis som för en diskriminerad union. Du kan använda aktiva mönster för att dela upp data på ett anpassat sätt för varje partition.

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

Kommentarer

I den tidigare syntaxen är identifierarna namn för partitioner av indata som representeras av argument, eller med andra ord namn för delmängder av uppsättningen med argumentens alla värden. Det kan finnas upp till sju partitioner i en aktiv mönsterdefinition. Uttrycket beskriver det formulär som data ska delas upp i. Du kan använda en aktiv mönsterdefinition för att definiera reglerna för att avgöra vilka av de namngivna partitionerna som värdena som anges som argument tillhör. Symbolerna (| och |) kallas bananklipp och funktionen som skapas av den här typen av let-bindning kallas för en aktiv identifierare.

Tänk till exempel på följande aktiva mönster med ett argument.

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

Du kan använda det aktiva mönstret i ett mönstermatchningsuttryck, som i följande exempel.

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

TestNumber 7
TestNumber 11
TestNumber 32

Utdata för det här programmet är följande:

7 is odd
11 is odd
32 is even

En annan användning av aktiva mönster är att dela upp datatyper på flera sätt, till exempel när samma underliggande data har olika möjliga representationer. Ett objekt kan till exempel Color delas upp i en RGB-representation eller en HSB-representation.

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"

Utdata från ovanstående program är följande:

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

I kombination gör dessa två sätt att använda aktiva mönster att du kan partitionera och dela upp data i rätt form och utföra lämpliga beräkningar på lämpliga data i den form som passar bäst för beräkningen.

De resulterande mönstermatchningsuttrycken gör att data kan skrivas på ett bekvämt sätt som är mycket läsbart, vilket avsevärt förenklar potentiellt komplex förgrening och dataanalyskod.

Partiella aktiva mönster

Ibland behöver du bara partitioneras en del av indatautrymmet. I så fall skriver du en uppsättning partiella mönster som var och en matchar vissa indata men inte matchar andra indata. Aktiva mönster som inte alltid skapar ett värde kallas partiella aktiva mönster. De har ett returvärde som är en alternativtyp. Om du vill definiera ett partiellt aktivt mönster använder du ett jokertecken (_) i slutet av listan med mönster i bananklippen. Följande kod illustrerar användningen av ett partiellt aktivt mönster.

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"

Utdata från föregående exempel är följande:

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

När du använder partiella aktiva mönster kan de enskilda valen ibland vara åtskilda eller ömsesidigt uteslutande, men det behöver de inte vara. I följande exempel är mönstret Kvadrat och mönstret Kub inte uppdelade, eftersom vissa tal är både rutor och kuber, till exempel 64. Följande program använder AND-mönstret för att kombinera kvadrat- och kubmönstren. Den skriver ut alla heltal upp till 1 000 som är både kvadrater och kuber, samt de som bara är kuber.

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)

Utdata är följande:

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

Parameteriserade aktiva mönster

Aktiva mönster tar alltid minst ett argument för objektet som matchas, men de kan också ta ytterligare argument, i vilket fall det namnparameteriserade aktiva mönstret gäller. Med ytterligare argument kan ett allmänt mönster vara specialiserat. Till exempel inkluderar aktiva mönster som använder reguljära uttryck för att parsa strängar ofta reguljära uttryck som en extra parameter, som i följande kod, som också använder det partiella aktiva mönstret Integer som definierades i föregående kodexempel. I det här exemplet ges strängar som använder reguljära uttryck för olika datumformat för att anpassa det allmänna aktiva ParseRegex-mönstret. Det aktiva heltalsmönstret används för att konvertera de matchade strängarna till heltal som kan skickas till DateTime-konstruktorn.

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())

Utdata från föregående kod är följande:

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

Aktiva mönster är inte bara begränsade till mönstermatchningsuttryck, du kan också använda dem på let-bindings.

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")

Utdata från föregående kod är följande:

Hello, random citizen!
Hello, George!

Observera dock att endast enstaka aktiva mönster kan parametriseras.

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

Struct-representationer för partiella aktiva mönster

Som standard returnerar partiella aktiva mönster ett option värde, vilket innebär en allokering för Some värdet på en lyckad matchning. Du kan också använda ett värdealternativ som ett returvärde med hjälp av Struct attributet:

open System

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

Attributet måste anges eftersom användningen av en struct-retur inte härleds från att helt enkelt ändra returtypen till ValueOption. Mer information finns i RFC FS-1039.

Se även