Aktive Muster

Aktive Muster ermöglichen Ihnen das Definieren benannter Partitionen, die Eingabedaten so unterteilen, dass Sie diese Namen in einem Musterabgleichsausdruck genauso wie bei einer Unterscheidungs-Union verwenden können. Mit aktiven Mustern können Sie Daten benutzerdefiniert für jede Partition zerlegen.

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

Bemerkungen

In der vorherigen Syntax sind die Bezeichner Namen für Partitionen der Eingabedaten, die durch Argumente dargestellt werden, oder, in anderen Worten, Namen für Teilmengen der Menge aller Werte der Argumente. Die Definition eines aktiven Musters kann bis zu sieben Partitionen enthalten. Der Ausdruck beschreibt die Form, in die die Daten zerlegt werden sollen. Sie können in einer Definition eines aktiven Musters Regeln festlegen, um zu bestimmen, zu welcher der benannten Partitionen 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

Ein weiterer Zweck aktiver Muster ist die Zerlegung von Datentypen auf mehrere Arten, z. B. wenn dieselben zugrunde liegenden Daten verschiedene Darstellungsmöglichkeiten haben. Ein Color-Objekt kann beispielsweise in eine RGB- oder HSB-Darstellung zerlegt 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

Mitunter müssen Sie nur einen Teil des Eingabebereichs partitionieren. In diesem Fall schreiben Sie eine Reihe von Teilmustern, von denen jedes mit einigen Eingaben übereinstimmt, aber bei anderen Eingaben fehlschlägt. Aktive Muster, die nicht immer einen Wert generieren, werden partielle aktive Muster genannt. Ihr Rückgabewert ist ein Optionstyp. 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 partiellen 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 Verwendung partieller aktiver Muster können die einzelnen Auswahlmöglichkeiten mitunter disjunkt sein oder sich gegenseitig ausschließen, was jedoch nicht der Fall sein muss. Im folgenden Beispiel sind das Muster „Square“ und das Muster „Cube“ nicht disjunkt, da einige Zahlen sowohl Quadrate als auch Würfel sind, wie z. B. 64. Das folgende Programm verwendet das AND-Muster, um die Muster „Square“ und „Cube“ zu kombinieren. Es gibt alle ganzen Zahlen bis 1.000 aus, die sowohl Quadrate als auch Würfel sind, sowie solche, die nur Würfel 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. Mit zusätzlichen Argumenten kann ein allgemeines Muster spezialisiert werden. Aktive Muster, die reguläre Ausdrücke zum Parsen von Zeichenfolgen verwenden, enthalten beispielsweise häufig den regulären Ausdruck als zusätzlichen Parameter, wie beispielsweise im folgenden Code, der auch das im vorherigen Codebeispiel definierte partielle aktive Muster Integer verwendet. 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

Strukturdarstellungen für partielle aktive Muster

Standardmäßig geben partielle aktive Muster einen option-Wert zurück, der bei einer erfolgreichen Übereinstimmung eine Zuteilung für den Some-Wert nach sich zieht. Alternativ können Sie auch eine Wertoption mithilfe des Struct-Attributs als Rückgabewert 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 einfach dadurch abgeleitet wird, dass der Rückgabetyp in ValueOption geändert wird. Weitere Informationen finden Sie unter RFC FS-1039.

Weitere Informationen