Actieve patronen

Met actieve patronen kunt u benoemde partities definiëren die invoergegevens onderverdelen, zodat u deze namen in een patroonkoppelingsexpressie kunt gebruiken, net zoals voor een gediscrimineerde samenvoeging. U kunt actieve patronen gebruiken om gegevens op een aangepaste manier op te splitsen voor elke partitie.

Syntaxis

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

Opmerkingen

In de vorige syntaxis zijn de id's namen voor partities van de invoergegevens die worden vertegenwoordigd door argumenten, of, met andere woorden, namen voor subsets van de set met alle waarden van de argumenten. Er kunnen maximaal zeven partities zijn in een actieve patroondefinitie. In de expressie wordt het formulier beschreven waarin de gegevens moeten worden opgesplitst. U kunt een actieve patroondefinitie gebruiken om de regels te definiëren voor het bepalen van welke van de benoemde partities de waarden zijn opgegeven als argumenten. De symbolen (| en |) worden bananenclips genoemd en de functie die door dit type letbinding wordt gemaakt, wordt een actieve herkenner genoemd.

Bekijk bijvoorbeeld het volgende actieve patroon met een argument.

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

U kunt het actieve patroon gebruiken in een patroonkoppelingsexpressie, zoals in het volgende voorbeeld.

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

TestNumber 7
TestNumber 11
TestNumber 32

De uitvoer van dit programma is als volgt:

7 is odd
11 is odd
32 is even

Een ander gebruik van actieve patronen is om gegevenstypen op meerdere manieren op te splitsen, bijvoorbeeld wanneer dezelfde onderliggende gegevens verschillende representaties hebben. Een object kan bijvoorbeeld Color worden opgesplitst in een RGB-weergave of een HSB-weergave.

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"

De uitvoer van het bovenstaande programma is als volgt:

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

In combinatie kunt u met deze twee manieren actieve patronen gebruiken om gegevens te partitioneren en op te splitsen in alleen het juiste formulier en de juiste berekeningen uit te voeren op de juiste gegevens in de vorm die het handigst is voor de berekening.

Met de resulterende patroonkoppelingsexpressies kunnen gegevens op een handige manier worden geschreven die zeer leesbaar is, waardoor mogelijk complexe vertakkings- en gegevensanalysecode aanzienlijk wordt vereenvoudigd.

Gedeeltelijke actieve patronen

Soms moet u slechts een deel van de invoerruimte partitioneren. In dat geval schrijft u een set gedeeltelijke patronen die allemaal overeenkomen met bepaalde invoer, maar die niet overeenkomen met andere invoerwaarden. Actieve patronen die niet altijd een waarde produceren, worden gedeeltelijke actieve patronen genoemd. Ze hebben een retourwaarde die een optietype is. Als u een gedeeltelijk actief patroon wilt definiëren, gebruikt u een jokerteken (_) aan het einde van de lijst met patronen in de banaanclips. De volgende code illustreert het gebruik van een gedeeltelijk actief patroon.

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"

De uitvoer van het vorige voorbeeld is als volgt:

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

Wanneer u gedeeltelijke actieve patronen gebruikt, kunnen de afzonderlijke keuzes soms niet aan elkaar worden gekoppeld of elkaar uitsluiten, maar ze hoeven niet te zijn. In het volgende voorbeeld zijn het patroon Vierkant en de patroonkubus niet ontkoppeld, omdat sommige getallen zowel kwadraten als kubussen zijn, zoals 64. In het volgende programma wordt het AND-patroon gebruikt om de vierkants- en kubuspatronen te combineren. Hiermee worden alle gehele getallen afgedrukt tot 1000 die zowel vierkanten als kubussen zijn, evenals de gehele getallen die alleen kubussen zijn.

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)

De uitvoer is als volgt:

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

Geparameteriseerde actieve patronen

Actieve patronen nemen altijd ten minste één argument voor het item dat wordt vergeleken, maar ze kunnen ook aanvullende argumenten aannemen, in welk geval het geparameteriseerde actieve patroon van toepassing is. Met aanvullende argumenten kan een algemeen patroon worden gespecialiseerd. Actieve patronen die gebruikmaken van reguliere expressies om tekenreeksen te parseren, bevatten bijvoorbeeld vaak de reguliere expressie als extra parameter, zoals in de volgende code, waarbij ook het gedeeltelijke actieve patroon Integer wordt gebruikt dat in het vorige codevoorbeeld is gedefinieerd. In dit voorbeeld worden tekenreeksen die gebruikmaken van reguliere expressies voor verschillende datumnotaties gegeven om het algemene actieve ParseRegex-patroon aan te passen. Het actieve patroon Integer wordt gebruikt om de overeenkomende tekenreeksen te converteren naar gehele getallen die kunnen worden doorgegeven aan de DateTime-constructor.

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

De uitvoer van de vorige code is als volgt:

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

Actieve patronen zijn niet alleen beperkt tot expressies die overeenkomen met patronen. U kunt deze ook gebruiken voor let-bindingen.

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

De uitvoer van de vorige code is als volgt:

Hello, random citizen!
Hello, George!

Houd er echter rekening mee dat alleen actieve patronen in één geval kunnen worden geparameteriseerd.

// 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-weergaven voor gedeeltelijke actieve patronen

Standaard retourneren gedeeltelijke actieve patronen een option waarde, waarbij een toewijzing voor de Some waarde voor een geslaagde overeenkomst wordt gebruikt. U kunt ook een waardeoptie als retourwaarde gebruiken via het gebruik van het Struct kenmerk:

open System

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

Het kenmerk moet worden opgegeven, omdat het gebruik van een struct-return niet wordt afgeleid van het gewoon wijzigen van het retourtype in ValueOption. Zie RFC FS-1039 voor meer informatie.

Zie ook