Musterabgleich

Muster sind Regeln zum Transformieren von Eingabedaten. Sie werden in F# verwendet, um Daten mit logischen Strukturen zu vergleichen, Daten in einzelne Bestandteile zu zerlegen oder auf unterschiedliche Weise Informationen aus Daten zu extrahieren.

Bemerkungen

Muster werden in vielen Sprachkonstrukten verwendet, z. B. im match-Ausdruck. Sie werden verwendet, wenn Argumente für Funktionen in let-Bindungen oder Lambda-Ausdrücken verarbeitet werden, sowie in den dem try...with-Ausdruck zugeordneten Ausnahmehandlern. Weitere Informationen finden Sie unter Abgleichen von Ausdrücken, let-Bindungen, Lambdaausdrücke: Das fun-Schlüsselwort und Ausnahmen: Der try...with-Ausdruck.

Beispielsweise wird das Muster im match-Ausdruck hinter dem Pipesymbol angegeben.

match expression with
| pattern [ when condition ] -> result-expression
...

Jedes Muster fungiert als Regel zum Transformieren von Eingaben. Im match-Ausdruck wird jedes Muster einzeln untersucht, um zu ermitteln, ob die Eingabedaten mit dem Muster kompatibel sind. Wenn eine Übereinstimmung gefunden wird, wird der Ergebnisausdruck ausgeführt. Wenn keine Übereinstimmung gefunden wird, wird die nächste Musterregel getestet. Der optionale Teil „when condition“ wird unter Abgleichen von Ausdrücken erläutert.

In der folgenden Tabelle werden unterstützte Muster aufgeführt. Zur Laufzeit wird die Eingabe anhand jedes der folgenden Muster in der in der Tabelle aufgeführten Reihenfolge überprüft. Die Muster werden rekursiv vom ersten bis zum letzten Muster im Code und von links nach rechts in den einzelnen Zeilen angewendet.

Name Beschreibung des Dataflows Beispiel
Konstantenmuster Ein beliebiges numerisches Literal, Zeichenliteral oder Zeichenfolgenliteral, eine Enumerationskonstante oder ein definierter Literalbezeichner. 1.0, "test", 30, Color.Red
Bezeichnermuster Der Wert eines Falls einer Unterscheidungs-Union, eine Ausnahmebezeichnung oder ein Fall eines aktiven Musters. Some(x)

Failure(msg)
Variablenmuster identifier a
as-Muster Muster as Bezeichner (a, b) as tuple1
OR-Muster Muster1 | Muster2 ([h] | [h; _])
AND-Muster pattern1 & pattern2 (a, b) & (_, "test")
Cons-Muster Bezeichner :: Listenbezeichner h :: t
Listenmuster [ Muster_1; ... ; Muster_n ] [ a; b; c ]
Arraymuster [| Muster_1; ..; Muster_n |] [| a; b; c |]
Muster in Klammern ( Muster ) ( a )
Tupelmuster ( Muster_1, ... , Muster_n ) ( a, b )
Datensatzmuster { Bezeichner1 = Muster_1; ... ; Bezeichner_n = Muster_n } { Name = name; }
Platzhaltermuster _ _
Muster zusammen mit Typanmerkung Muster : Typ a : int
Typtestmuster :? Typ [ as Bezeichner ] :? System.DateTime as dt
NULL-Muster NULL null
Nameof-Muster nameof expr nameof str

Konstantenmuster

Konstantenmuster sind numerische Literale, Zeichenliterale und Zeichenfolgenliterale, Enumerationskonstanten (einschließlich Enumerationstypnamen). Ein match-Ausdruck, der nur über Konstantenmuster verfügt, ist mit case-Anweisungen in anderen Sprachen vergleichbar. Die Eingabe wird mit dem Literalwert verglichen, und das Muster stimmt überein, wenn die Werte gleich sind. Der Typ des Literals muss mit dem Typ der Eingabe kompatibel sein.

Im folgenden Beispiel wird die Verwendung von Literalmustern veranschaulicht, und es werden außerdem ein Variablenmuster und ein OR-Muster verwendet.

[<Literal>]
let Three = 3

let filter123 x =
    match x with
    // The following line contains literal patterns combined with an OR pattern.
    | 1 | 2 | Three -> printfn "Found 1, 2, or 3!"
    // The following line contains a variable pattern.
    | var1 -> printfn "%d" var1

for x in 1..10 do filter123 x

Ein weiteres Beispiel für ein Literalmuster ist ein auf Enumerationskonstanten basierendes Muster. Wenn Sie Enumerationskonstanten verwenden, müssen Sie den Enumerationstypnamen angeben.

type Color =
    | Red = 0
    | Green = 1
    | Blue = 2

let printColorName (color:Color) =
    match color with
    | Color.Red -> printfn "Red"
    | Color.Green -> printfn "Green"
    | Color.Blue -> printfn "Blue"
    | _ -> ()

printColorName Color.Red
printColorName Color.Green
printColorName Color.Blue

Bezeichnermuster

Wenn das Muster eine Zeichenfolge ist, die einen gültigen Bezeichner bildet, bestimmt die Form des Bezeichners, wie das Muster verglichen wird. Wenn der Bezeichner länger als ein einzelnes Zeichen ist und mit einem Großbuchstaben beginnt, versucht der Compiler, eine Übereinstimmung mit dem Bezeichnermuster zu ermitteln. Der Bezeichner für dieses Muster kann ein Wert sein, der mit dem Literal-Attribut, einem Unterscheidungs-Union-Fall, einem Ausnahmebezeichner oder einem Fall eines aktiven Musters markiert wurde. Wenn kein übereinstimmender Bezeichner gefunden wird, schlägt der Vergleich fehl, und die nächste Musterregel, das Variablenmuster, wird mit der Eingabe verglichen.

Unterscheidungs-Union-Muster können einfache benannte Fälle sein oder über einen Wert bzw. über ein mehrere Werte enthaltendes Tupel verfügen. Wenn ein Wert vorhanden ist, müssen Sie einen Bezeichner für den Wert angeben. Im Fall eines Tupels müssen Sie ein Tupelmuster mit einem Bezeichner für jedes Element des Tupels oder einen Bezeichner mit einem Feldnamen für eine oder mehrere benannte Union-Felder angeben. Entsprechende Beispiele finden Sie in den Codebeispielen dieses Abschnitts.

Der option-Typ ist eine Unterscheidungs-Union mit den beiden Fällen Some und None. Ein Fall (Some) verfügt über einen Wert, der andere Fall (None) ist jedoch lediglich ein benannter Fall. Daher muss Some über eine Variable für den dem Fall Some zugeordneten Wert verfügen, None muss jedoch als None angegeben werden. Im folgenden Code wird der Variablen var1 der Wert zugewiesen, der durch den Vergleich mit dem Fall Some ermittelt wurde.

let printOption (data : int option) =
    match data with
    | Some var1  -> printfn "%d" var1
    | None -> ()

Im folgenden Beispiel enthält die Unterscheidungs-Union PersonName eine Kombination von Zeichenfolgen und Zeichen, die mögliche Formen von Namen darstellen. Die Fälle der Unterscheidungs-Union lauten FirstOnly, LastOnly und FirstLast.

type PersonName =
    | FirstOnly of string
    | LastOnly of string
    | FirstLast of string * string

let constructQuery personName =
    match personName with
    | FirstOnly(firstName) -> printf "May I call you %s?" firstName
    | LastOnly(lastName) -> printf "Are you Mr. or Ms. %s?" lastName
    | FirstLast(firstName, lastName) -> printf "Are you %s %s?" firstName lastName

Für Unterscheidungs-Unions, die über benannte Felder verfügen, verwenden Sie das Gleichheitszeichen (=), um den Wert eines benannten Felds zu extrahieren. Betrachten Sie zum Beispiel eine Unterscheidungs-Union mit einer Deklaration wie der folgenden.

type Shape =
    | Rectangle of height : float * width : float
    | Circle of radius : float

Sie können die benannten Felder in einem Musterabgleichsausdruck wie folgt verwenden.

let matchShape shape =
    match shape with
    | Rectangle(height = h) -> printfn $"Rectangle with length %f{h}"
    | Circle(r) -> printfn $"Circle with radius %f{r}"

Die Verwendung des benannten Felds ist optional. Im vorherigen Beispiel haben Circle(r) und Circle(radius = r) die gleiche Auswirkung.

Wenn Sie mehrere Felder angeben, verwenden Sie das Semikolon (;) als Trennzeichen.

match shape with
| Rectangle(height = h; width = w) -> printfn $"Rectangle with height %f{h} and width %f{w}"
| _ -> ()

Mit aktiven Mustern können Sie komplexere benutzerdefinierte Musterabgleiche definieren. Weitere Informationen zu aktiven Mustern finden Sie unter Aktive Muster.

Der Fall, in dem der Bezeichner eine Ausnahme ist, wird beim Mustervergleich im Kontext von Ausnahmehandlern verwendet. Informationen zum Musterabgleich bei der Ausnahmebehandlung finden Sie unter Ausnahmen: Der try...with-Ausdruck.

Variablenmuster

Im Variablenmuster wird dem zu vergleichenden Wert ein Variablenname zugewiesen, der dann im Ausführungsausdruck auf der rechten Seite des Symbols -> verwendet werden kann. Ein Variablenmuster an sich stimmt mit jeder Eingabe überein, jedoch sind Variablenmuster häufig in anderen Mustern enthalten und ermöglichen somit komplexere Strukturen, z. B. Tupel und Arrays, die in Variablen zerlegt werden sollen.

Im folgenden Beispiel wird ein Variablenmuster in einem Tupelmuster veranschaulicht.

let function1 x =
    match x with
    | (var1, var2) when var1 > var2 -> printfn "%d is greater than %d" var1 var2
    | (var1, var2) when var1 < var2 -> printfn "%d is less than %d" var1 var2
    | (var1, var2) -> printfn "%d equals %d" var1 var2

function1 (1,2)
function1 (2, 1)
function1 (0, 0)

as-Muster

Das as-Muster ist ein Muster, an das eine as-Klausel angefügt ist. Die as-Klausel bindet den übereinstimmenden Wert an einen Namen, der im Ausführungsausdruck eines match-Ausdrucks verwendet werden kann. Falls das Muster in einer let-Bindung verwendet wird, wird stattdessen der Name dem lokalen Bereich als Bindung hinzugefügt.

Im folgenden Beispiel wird ein as-Muster verwendet.

let (var1, var2) as tuple1 = (1, 2)
printfn "%d %d %A" var1 var2 tuple1

OR-Muster

Das OR-Muster wird verwendet, wenn Eingabedaten mit mehreren Mustern übereinstimmen können und als Ergebnis der gleiche Code ausgeführt werden soll. Die Typen beider Seiten des OR-Musters müssen kompatibel sein.

Das OR-Muster wird im folgenden Beispiel veranschaulicht.

let detectZeroOR point =
    match point with
    | (0, 0) | (0, _) | (_, 0) -> printfn "Zero found."
    | _ -> printfn "Both nonzero."
detectZeroOR (0, 0)
detectZeroOR (1, 0)
detectZeroOR (0, 10)
detectZeroOR (10, 15)

AND-Muster

Das AND-Muster erfordert, dass die Eingabe mit zwei Mustern übereinstimmt. Die Typen beider Seiten des AND-Musters müssen kompatibel sein.

Das folgende Beispiel entspricht dem im Abschnitt zu Tupelmustern weiter unten in diesem Thema gezeigten detectZeroTuple, jedoch werden hier mit dem AND-Muster sowohl var1 als auch var2 als Werte abgerufen.

let detectZeroAND point =
    match point with
    | (0, 0) -> printfn "Both values zero."
    | (var1, var2) & (0, _) -> printfn "First value is 0 in (%d, %d)" var1 var2
    | (var1, var2)  & (_, 0) -> printfn "Second value is 0 in (%d, %d)" var1 var2
    | _ -> printfn "Both nonzero."
detectZeroAND (0, 0)
detectZeroAND (1, 0)
detectZeroAND (0, 10)
detectZeroAND (10, 15)

Cons-Muster

Mit dem cons-Muster wird eine Liste in das erste Element, den Kopf, und eine Liste aus den restlichen Elementen, das Ende, zerlegt.

let list1 = [ 1; 2; 3; 4 ]

// This example uses a cons pattern and a list pattern.
let rec printList l =
    match l with
    | head :: tail -> printf "%d " head; printList tail
    | [] -> printfn ""

printList list1

Listenmuster

Mit dem Listenmuster können Listen in eine Reihe von Elementen zerlegt werden. Das Listenmuster selbst darf nur mit Listen einer bestimmten Anzahl von Elementen übereinstimmen.

// This example uses a list pattern.
let listLength list =
    match list with
    | [] -> 0
    | [ _ ] -> 1
    | [ _; _ ] -> 2
    | [ _; _; _ ] -> 3
    | _ -> List.length list

printfn "%d" (listLength [ 1 ])
printfn "%d" (listLength [ 1; 1 ])
printfn "%d" (listLength [ 1; 1; 1; ])
printfn "%d" (listLength [ ] )

Arraymuster

Das Arraymuster ähnelt dem Listenmuster, und es kann zum Zerlegen von Arrays einer bestimmten Länge verwendet werden.

// This example uses array patterns.
let vectorLength vec =
    match vec with
    | [| var1 |] -> var1
    | [| var1; var2 |] -> sqrt (var1*var1 + var2*var2)
    | [| var1; var2; var3 |] -> sqrt (var1*var1 + var2*var2 + var3*var3)
    | _ -> failwith (sprintf "vectorLength called with an unsupported array size of %d." (vec.Length))

printfn "%f" (vectorLength [| 1. |])
printfn "%f" (vectorLength [| 1.; 1. |])
printfn "%f" (vectorLength [| 1.; 1.; 1.; |])
printfn "%f" (vectorLength [| |] )

Muster in Klammern

Muster können in Klammern eingeschlossen werden, um die gewünschte Assoziativität zu erreichen. Im folgenden Beispiel werden Klammern verwendet, um die Assoziativität zwischen einem AND-Muster und einem Cons-Muster zu steuern.

let countValues list value =
    let rec checkList list acc =
       match list with
       | (elem1 & head) :: tail when elem1 = value -> checkList tail (acc + 1)
       | head :: tail -> checkList tail acc
       | [] -> acc
    checkList list 0

let result = countValues [ for x in -10..10 -> x*x - 4 ] 0
printfn "%d" result

Tupelmuster

Mithilfe des Tupelmusters werden Eingabe in Tupelform verglichen. Es ermöglicht das Zerlegen des Tupels in seine Bestandteile mithilfe von Musterabgleichsvariablen für die einzelnen Positionen im Tupel.

Im folgenden Beispiel wird die Verwendung des Tupelmusters veranschaulicht, und es werden außerdem Literalmuster, Variablenmuster und das Platzhaltermuster verwendet.

let detectZeroTuple point =
    match point with
    | (0, 0) -> printfn "Both values zero."
    | (0, var2) -> printfn "First value is 0 in (0, %d)" var2
    | (var1, 0) -> printfn "Second value is 0 in (%d, 0)" var1
    | _ -> printfn "Both nonzero."
detectZeroTuple (0, 0)
detectZeroTuple (1, 0)
detectZeroTuple (0, 10)
detectZeroTuple (10, 15)

Datensatzmuster

Mit dem Datensatzmuster werden Datensätze zerlegt, um die Werte von Feldern zu extrahieren. Das Muster muss nicht auf alle Felder des Datensatzes verweisen. Weggelassene Felder werden nicht verglichen und nicht extrahiert.

// This example uses a record pattern.

type MyRecord = { Name: string; ID: int }

let IsMatchByName record1 (name: string) =
    match record1 with
    | { MyRecord.Name = nameFound; MyRecord.ID = _; } when nameFound = name -> true
    | _ -> false

let recordX = { Name = "Parker"; ID = 10 }
let isMatched1 = IsMatchByName recordX "Parker"
let isMatched2 = IsMatchByName recordX "Hartono"

Platzhaltermuster

Das Platzhaltermuster wird durch den Unterstrich (_) dargestellt und stimmt wie das Variablenmuster mit jeder Eingabe überein, außer dass die Eingabe verworfen wird, anstatt einer Variablen zugewiesen zu werden. Das Platzhaltermuster wird oft innerhalb anderer Muster als Platzhalter für Werte verwendet, die im Ausdruck auf der rechten Seite des Symbols -> nicht benötigt werden. Das Platzhaltermuster wird außerdem häufig am Ende einer Liste von Mustern als Übereinstimmung für jede nicht übereinstimmende Eingabe verwendet. Das Platzhaltermuster wird in vielen Codebeispielen dieses Themas veranschaulicht. Ein Beispiel finden Sie im vorangehenden Code.

Muster mit Typanmerkungen

Muster können Typanmerkungen aufweisen. Diese verhalten sich wie andere Typanmerkungen und steuern Typrückschluss wie diese. Typanmerkungen in Mustern müssen in Klammern eingeschlossen werden. Im folgenden Code wird ein Muster veranschaulicht, das eine Typanmerkung aufweist.

let detect1 x =
    match x with
    | 1 -> printfn "Found a 1!"
    | (var1 : int) -> printfn "%d" var1
detect1 0
detect1 1

Typtestmuster

Das Typtestmuster wird verwendet, um die Eingabe anhand eines Typs zu vergleichen. Wenn der Eingabetyp mit dem im Muster angegebenen Typ oder einem von diesem abgeleiteten Typ übereinstimmt, ist der Vergleich erfolgreich.

Das Typtestmuster wird im folgenden Beispiel veranschaulicht.

open System.Windows.Forms

let RegisterControl(control:Control) =
    match control with
    | :? Button as button -> button.Text <- "Registered."
    | :? CheckBox as checkbox -> checkbox.Text <- "Registered."
    | _ -> ()

Wenn Sie nur überprüfen, ob ein Bezeichner einen bestimmten abgeleiteten Typ aufweist, benötigen Sie den as identifier-Teil des Musters nicht, wie im folgenden Beispiel gezeigt:

type A() = class end
type B() = inherit A()
type C() = inherit A()

let m (a: A) =
    match a with
    | :? B -> printfn "It's a B"
    | :? C -> printfn "It's a C"
    | _ -> ()

NULL-Muster

Das NULL-Muster wird mit dem NULL-Wert verglichen, der beim Arbeiten mit Typen, die NULL-Werte zulassen, vorhanden sein kann. NULL-Muster werden häufig bei der Zusammenarbeit mit .NET Framework-Code verwendet. Beispielsweise kann der Rückgabewert einer .NET-API die Eingabe für einen match-Ausdruck sein. Die Steuerung des Programmablaufs kann von Eigenschaften des Rückgabewerts abhängig gemacht werden, beispielsweise davon, ob der Rückgabewert NULL ist. Mithilfe des NULL-Musters können Sie verhindern, dass NULL-Werte an den Rest des Programms weitergegeben werden.

Im folgenden Beispiel werden das NULL-Muster und das Variablenmuster verwendet.

let ReadFromFile (reader : System.IO.StreamReader) =
    match reader.ReadLine() with
    | null -> printfn "\n"; false
    | line -> printfn "%s" line; true

let fs = System.IO.File.Open("..\..\Program.fs", System.IO.FileMode.Open)
let sr = new System.IO.StreamReader(fs)
while ReadFromFile(sr) = true do ()
sr.Close()

Nameof-Muster

Das nameof-Muster wird mit einer Zeichenfolge abgeglichen, wenn ihr Wert dem Ausdruck entspricht, der dem nameof-Schlüsselwort folgt. Beispiel:

let f (str: string) =
    match str with
    | nameof str -> "It's 'str'!"
    | _ -> "It is not 'str'!"

f "str" // matches
f "asdf" // does not match

Informationen zu den Elementen, deren Namen Sie verwenden können, finden Sie unter dem nameof-Operator.

Siehe auch