Freigeben über


Aktive Muster

Aktive Muster ermöglichen Ihnen das Definieren benannter Partitionen, die Eingabedaten so unterteilen, dass Sie diese Namen in einem Musterabgleichs-Ausdruck genauso wie bei einer Unterscheidungs-Union verwenden können. Sie können aktive Muster verwenden, um Daten auf angepasste Weise für jede Partition zu dekompilieren.

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

Hinweise

In der vorhergehenden Syntax dienen die Bezeichner als Namen für Partitionen der Eingabedaten, die durch Argumentedargestellt werden. Mit anderen Worten, sie sind Namen für Teilmengen der Menge aller Werte der Argumente. Es können bis zu sieben Partitionen in einer aktiven Musterdefinition vorhanden sein. Der Ausdruck beschreibt die Form, in die die Daten zerlegt werden sollen. Sie können eine aktive Musterdefinition verwenden, um die Regeln zum Bestimmen der benannten Partitionen zu definieren, zu denen die als Argumente angegebenen Werte gehören. Die Symbole (| und |) werden als Bananenklammern bezeichnet. Die Funktion, die durch diese Art der Let-Bindung erstellt wird, wird als aktives Erkennungsmodul bezeichnet.

Betrachten Sie als Beispiel das folgende aktive Muster mit einem Argument.

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

Sie können das aktive Muster wie im folgenden Beispiel in einem Musterabgleichsausdruck verwenden.

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

TestNumber 7
TestNumber 11
TestNumber 32

Die Ausgabe dieses Programms ist wie folgt:

7 is odd
11 is odd
32 is even

Eine weitere Verwendung von aktiven Mustern besteht darin, Datentypen auf mehrere Arten zu dekompilieren, z. B. wenn dieselben zugrunde liegenden Daten über verschiedene mögliche Darstellungen verfügen. Ein Color-Objekt kann beispielsweise in eine RGB-Darstellung oder eine HSB-Darstellung dekompiliert werden.

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"

Die Ausgabe des obigen Programms ist wie folgt:

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 Kombination ermöglichen Ihnen diese beiden Arten der Verwendung aktiver Muster die Partitionierung und Zerlegung von Daten in genau die richtige Form und die Durchführung der entsprechenden Berechnungen mit den entsprechenden Daten in der für die Berechnung am besten geeigneten Form.

Die sich daraus ergebenden Musterabgleichsausdrücke ermöglichen es, Daten auf bequeme und gut lesbare Weise zu schreiben, wodurch potenziell komplexer Code für Verzweigungen und Datenanalysen erheblich vereinfacht wird.

Partielle aktive Muster

Manchmal müssen Sie nur einen Teil des Eingaberaums partitionieren. In diesem Fall schreiben Sie eine Reihe von Teilmustern, die jeweils mit einigen Eingaben übereinstimmen, aber nicht mit anderen Eingaben übereinstimmen. Aktive Muster, die nicht immer einen Wert erzeugen, werden teilweise aktive Mustergenannt; sie haben einen Rückgabewert, der ein Optionstyp ist. Um ein partielles aktives Muster zu definieren, fügen Sie ein Platzhalterzeichen (_) am Ende der Liste der Muster innerhalb der Bananenklammern ein. Der folgende Code veranschaulicht die Verwendung eines teilweisen aktiven Musters.

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"

Die Ausgabe des vorherigen Beispiels ist wie folgt:

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

Bei der Verwendung von partiellen aktiven Mustern kann es vorkommen, dass die einzelnen Auswahlmöglichkeiten nicht miteinander verbunden oder sich gegenseitig ausschließen, aber sie müssen nicht sein. Im folgenden Beispiel sind das Muster Quadrat und der Musterwürfel nicht getrennt, da einige Zahlen sowohl Quadrate als auch Würfel sind, z. B. 64. Das folgende Programm verwendet das AND-Muster, um die Muster „Square“ und „Cube“ zu kombinieren. Es druckt alle ganzen Zahlen bis zu 1000 aus, die sowohl Quadratzahlen als auch Kubikzahlen sind, sowie diejenigen, die nur Kubikzahlen sind.

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)

Die Ausgabe lautet wie folgt:

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

Parametrisierte aktive Muster

Aktive Muster benötigen stets mindestens ein Argument für das Element, mit dem der Abgleich erfolgt, aber sie können auch zusätzliche Argumente enthalten. In diesem Fall ist von einem parametrisierten aktiven Muster die Rede. Zusätzliche Argumente ermöglichen es, ein allgemeines Muster zu spezialisieren. Beispielsweise verwenden aktive Muster, die reguläre Ausdrücke nutzen, um Zeichenfolgen zu analysieren, häufig den regulären Ausdruck als zusätzlichen Parameter, und im folgenden Code wird ebenfalls das teilweise aktive Muster Integer verwendet, das im vorherigen Codebeispiel definiert ist. In diesem Beispiel werden Zeichenfolgen, die reguläre Ausdrücke für verschiedene Datumsformate verwenden, angegeben, um das allgemeine aktive ParseRegex-Muster anzupassen. Das aktive Muster „Integer“ wird verwendet, um die übereinstimmenden Zeichenfolgen in ganze Zahlen zu konvertieren, die an den DateTime-Konstruktor übergeben werden können.

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

Die Ausgabe des obigen Codes ist wie folgt:

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

Aktive Muster sind nicht nur auf Musterabgleichsausdrücke beschränkt, sondern eignen sich auch für Let-Bindungen.

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

Die Ausgabe des obigen Codes ist wie folgt:

Hello, random citizen!
Hello, George!

Beachten Sie jedoch, dass nur aktive Muster für Einzelfälle parametrisiert werden können.

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

Rückgabetyp für partielle aktive Muster

Partielle aktive Muster geben Some () zurück, um eine Übereinstimmung anzugeben und andernfalls None.

Betrachten Sie dieses Spiel:

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

Das partielle aktive Muster dafür wäre:

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

Ab F# 9 können solche Muster auch boolzurückgeben:

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

Strukturdarstellungen für partielle aktive Muster

Wenn ein partielles aktives Muster eine option zurückgibt, beinhaltet dies bei erfolgreicher Übereinstimmung standardmäßig eine Zuordnung für den Some-Wert. Um dies zu vermeiden, können Sie eine Wertoption als Rückgabewert verwenden, indem Sie das Attribut Struct verwenden:

open System

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

Das Attribut muss angegeben werden, da die Verwendung einer Strukturrückgabe nicht davon abgeleitet wird, einfach den Rückgabetyp in ValueOptionzu ändern. Weitere Informationen finden Sie unter RFC FS-1039.

Weitere Informationen