Delen via


Patroonherkenning

Patronen zijn regels voor het transformeren van invoergegevens. Ze worden in F# gebruikt om gegevens op verschillende manieren te vergelijken met een logische structuur of structuren, gegevens op te delen in samenstellende delen of gegevens op verschillende manieren uit gegevens te extraheren.

Opmerkingen

Patronen worden gebruikt in veel taalconstructies, zoals de match expressie. Ze worden gebruikt wanneer u argumenten verwerkt voor functies in let bindingen, lambda-expressies en in de uitzonderingshandlers die zijn gekoppeld aan de try...with-expressie. Voor meer informatie, zie Match-expressies, let-bindingen, Lambda-expressies: het fun trefwoorden Uitzonderingen: de try...with expressie.

In de match-expressie volgt het patroon bijvoorbeeld het pijpsymbool.

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

Elk patroon fungeert als een regel voor het transformeren van invoer op een of andere manier. In de match expressie wordt elk patroon op zijn beurt onderzocht om te zien of de invoergegevens compatibel zijn met het patroon. Als er een overeenkomst wordt gevonden, wordt de resultaatexpressie uitgevoerd. Als er geen overeenkomst wordt gevonden, wordt de volgende patroonregel getest. De optionele "wanneer voorwaarde" wordt uitgelegd in Match-uitdrukkingen.

Ondersteunde patronen worden weergegeven in de volgende tabel. Tijdens de uitvoering wordt de invoer getest op elk van de volgende patronen in de volgorde die in de tabel wordt vermeld en worden patronen recursief toegepast, van eerste tot laatste, zoals ze in uw code worden weergegeven, en van links naar rechts voor de patronen op elke regel.

Naam Beschrijving Voorbeeld
Constante patroon Elke letterlijke numerieke, teken- of tekenreeks, een opsommingsconstante of een gedefinieerde letterlijke id 1.0,"test",30,Color.Red
Id-patroon Een casewaarde van een gediscrimineerde samenvoeging, een uitzonderingslabel of een actief patroon Some(x)

Failure(msg)
Variabel patroon identificatie a
as patroon patroon als identificatie (a, b) as tuple1
OR-patroon patroon1 | patroon2 ([h] | [h; _])
AND-patroon patroon1 & patroon2 (a, b) & (_, "test")
Nadelen patroon kenmerk :: lijstkenmerk h :: t
Lijstpatroon [ pattern_1; ... ; pattern_n ] [ a; b; c ]
Matrixpatroon [| pattern_1; ..; pattern_n |] [| a; b; c |]
Patroon tussen haakjes ( patroon ) ( a )
Tuple-patroon ( pattern_1, ... , pattern_n ) ( a, b )
Opnamepatroon { id1 = pattern_1; ... ; = identifier_npattern_n } { Name = name; }
Jokertekenpatroon _ _
Patroon samen met typeaantekening patroon: type a : int
Typetestpatroon :? type [ als identificator ] :? System.DateTime as dt
Null-patroon nul null
Naam van patroon naam van expr- nameof str

Constante patronen

Constante patronen zijn numerieke, teken- en tekenreeks letterlijke waarden, opsommingsconstanten (waarbij de naam van het opsommingstype is opgenomen). Een match-expressie met alleen constante patronen kan worden vergeleken met een case-instructie in andere talen. De invoer wordt vergeleken met de letterlijke waarde en het patroon komt overeen als de waarden gelijk zijn. Het type letterlijk moet compatibel zijn met het type invoer.

In het volgende voorbeeld ziet u het gebruik van letterlijke patronen en wordt ook een variabel patroon en een OR-patroon gebruikt.

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

Een ander voorbeeld van een letterlijk patroon is een patroon op basis van opsommingsconstanten. U moet de naam van het opsommingstype opgeven wanneer u opsommingsconstanten gebruikt.

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

Id-patronen

Als het patroon een tekenreeks is die een geldige identificator vormt, bepaalt de vorm van de identificator hoe het patroon wordt gematcht. Als de id langer is dan één teken en begint met een hoofdletter, probeert de compiler een overeenkomst te maken met het id-patroon. De identificatie voor dit patroon kan een waarde zijn die is gemarkeerd met het Literal-attribuut, een case van een gediscrimineerde unie, een uitzonderingsidentificatie of een actieve patrooncase. Als er geen overeenkomende id wordt gevonden, mislukt de overeenkomst en wordt de volgende patroonregel, het variabelepatroon, vergeleken met de invoer.

Gediscrimineerde samenvoegpatronen kunnen eenvoudige benoemde gevallen zijn of ze kunnen een waarde hebben, of een tuple met meerdere waarden. Als er een waarde is, moet u een id voor de waarde opgeven. In het geval van een tuple moet u een tuple-patroon opgeven met een id voor elk element van de tuple of een id met een veldnaam voor een of meer benoemde samenvoegvelden. Zie de codevoorbeelden in deze sectie voor voorbeelden.

Het option type is een gediscrimineerde vereniging met twee gevallen, Some en None. De ene case (Some) heeft een waarde, maar de andere (None) is slechts een benoemde case. Daarom moet Some een variabele hebben voor de waarde die is gekoppeld aan het Some geval, maar None moet op zichzelf staan. In de volgende code krijgt de variabele var1 de waarde die wordt verkregen door overeen te komen met het Some geval.

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

In het volgende voorbeeld bevat de PersonName gediscrimineerde samenvoeging een combinatie van tekenreeksen en tekens die mogelijke vormen van namen vertegenwoordigen. De gevallen van de gediscrimineerde unie zijn FirstOnly, LastOnlyen 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

Voor gediscrimineerde unies die benoemde velden hebben, gebruikt u het gelijkteken (=) om de waarde van een benoemd veld te extraheren. Denk bijvoorbeeld aan een gediscrimineerde vereniging met een verklaring zoals hieronder.

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

U kunt de benoemde velden als volgt gebruiken in een patroonkoppelingsexpressie.

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

Het gebruik van het benoemde veld is optioneel, dus in het vorige voorbeeld hebben zowel Circle(r) als Circle(radius = r) hetzelfde effect.

Wanneer u meerdere velden opgeeft, gebruikt u de puntkomma (;) als scheidingsteken.

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

Met actieve patronen kunt u complexere aangepaste patroonkoppelingen definiëren. Zie Actieve patronenvoor meer informatie over actieve patronen.

Het geval waarin de id een uitzondering is, wordt gebruikt in patroonkoppeling in de context van uitzonderingshandlers. Zie voor meer informatie over patroonvergelijking in uitzonderingsafhandeling Uitzonderingen: de try...with Expressie.

Variabele patronen

Het variabelepatroon wijst de waarde toe die overeenkomt met een naam van een variabele, die vervolgens beschikbaar is voor gebruik in de uitvoeringsexpressie rechts van het -> symbool. Een variabele patroon komt alleen overeen met elke invoer, maar variabele patronen verschijnen vaak in andere patronen, waardoor complexere structuren, zoals tuples en matrices, in variabelen kunnen worden opgesplitst.

In het volgende voorbeeld ziet u een variabel patroon binnen een tuple-patroon.

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)

als patroon

Het as-patroon is een patroon met een as component eraan toegevoegd. De as-component verbindt de overeenkomende waarde met een naam die kan worden gebruikt in de uitvoeringsexpressie van een match-expressie, of, in het geval dat dit patroon wordt gebruikt in een let binding, wordt de naam toegevoegd als een binding aan het lokale bereik.

In het volgende voorbeeld wordt een as patroon gebruikt.

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

OR-patroon

Het OR-patroon wordt gebruikt wanneer invoergegevens overeenkomen met meerdere patronen en u dezelfde code als een resultaat wilt uitvoeren. De typen van beide zijden van het OR-patroon moeten compatibel zijn.

In het volgende voorbeeld ziet u het OR-patroon.

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

Het AND-patroon vereist dat de invoer overeenkomt met twee patronen. De typen van de beide zijden van het AND-patroon moeten compatibel zijn.

Het volgende voorbeeld lijkt op detectZeroTuple weergegeven in de sectie Tuple-patroon verderop in dit onderwerp, maar hier worden zowel var1 als var2 verkregen als waarden met behulp van het AND-patroon.

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)

Nadelen patroon

Het nadelenpatroon wordt gebruikt om een lijst te ontleden in het eerste element, de kopen een lijst met de resterende elementen, de tail.

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

U kunt ook meerdere nadelenpatronen aan elkaar koppelen om lijsten te vinden die beginnen met specifieke reeksen elementen.

let charList = ['A'; 'B'; 'C'; 'D']

// This example demonstrates multiple cons patterns.
let matchChars xs =
    match xs with
    | 'A'::'B'::t -> printfn "starts with 'AB', rest: %A" t
    | 'A'::t -> printfn "starts with 'A', rest: %A" t
    | 'C'::'D'::t -> printfn "starts with 'CD', rest: %A" t
    | _ -> printfn "does not match"

matchChars charList
matchChars ['A'; 'X']
matchChars ['C'; 'D'; 'E']

Lijstpatroon

Met het lijstpatroon kunnen lijsten worden opgesplitst in een aantal elementen. Het lijstpatroon zelf kan alleen overeenkomen met lijsten met een specifiek aantal elementen.

// 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 [ ] )

Matrixpatroon

Het matrixpatroon lijkt op het lijstpatroon en kan worden gebruikt om matrices van een specifieke lengte te decomposeen.

// 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 [| |] )

Haakjes patroon

Haakjes kunnen rond patronen worden gegroepeerd om de gewenste associativiteit te bereiken. In het volgende voorbeeld worden haakjes gebruikt om associativiteit tussen een AND-patroon en een nadelenpatroon te beheren.

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

Tuple-patroon

Het tuplepatroon komt overeen met invoer in tuplevorm en stelt de tuple in staat om uit te splitsen in zijn samenstellende elementen door patroonvariabelen te gebruiken voor elke positie in de tuple.

In het volgende voorbeeld ziet u het tuple-patroon en worden ook letterlijke patronen, variabele patronen en het jokertekenpatroon gebruikt.

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)

Opnamepatroon

Het recordpatroon wordt gebruikt om records te decomponeren en de waarden van velden te extraheren. Het patroon hoeft niet naar alle velden van de record te verwijzen; eventuele weggelaten velden nemen alleen niet deel aan overeenkomende velden en worden niet geëxtraheerd.

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

Jokertekenpatroon

Het jokerteken wordt vertegenwoordigd door het onderstrepingsteken (_) en komt overeen met alle invoer, net als het variabelepatroon, behalve dat de invoer wordt verwijderd in plaats van toegewezen aan een variabele. Het jokertekenpatroon wordt vaak gebruikt in andere patronen als tijdelijke aanduiding voor waarden die niet nodig zijn in de expressie rechts van het -> symbool. Het wildcardpatroon wordt ook vaak aan het einde van een lijst met patronen gebruikt om elke niet-overeenkomende invoer te matchen. Het jokertekenpatroon wordt in veel codevoorbeelden in dit onderwerp gedemonstreerd. Zie de voorgaande code voor één voorbeeld.

De volgende code toont enkele aanvullende toepassingen van het jokertekenpatroon:

// Wildcard pattern matching "nothing" examples

// Example 1: Wildcard ignoring function parameters
let ignoreAllParams _ _ = "Ignores all input"

// Example 2: Wildcard in destructuring, ignoring elements
let getFirstOnly (first, _) = first

// Example 3: Using wildcard to ignore optional values
let handleEmpty opt =
    match opt with
    | Some _ -> "Has something"
    | None -> "Has nothing"

// Usage
printfn "%s" (ignoreAllParams 42 "test")
printfn "%d" (getFirstOnly (1, "ignored"))
printfn "%s" (handleEmpty None)

Patronen met typeaantekeningen

Patronen kunnen typeaantekeningen hebben. Deze gedragen zich als andere typeaantekeningen en sturen afleiding zoals andere typeaantekeningen. Haakjes zijn vereist rond typeaantekeningen in patronen.

Een patroon met een typeaantekening maakt gebruik van de syntaxis pattern : type en biedt informatie over het typetype compileren voor de typecontrole. Dit is uitsluitend een statische typeaantekening die helpt bij typedeductie. Er worden geen runtimetypen gecontroleerd of geconverteerd. De compiler gebruikt deze informatie tijdens de compilatie om het type van de patroonvariabele te bepalen.

De volgende code toont een patroon met een typeaantekening:

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

In dit voorbeeld (var1 : int) geeft u aan dat var1 de compiler van het type intis. Dit wordt opgelost tijdens het compileren en de gegenereerde code wordt var1 behandeld als een geheel getal in de overeenkomstexpressie. Dit patroon komt overeen met een geheel getal en verbindt deze aan var1.

Belangrijkste kenmerken:

  • Maakt gebruik van de syntaxis pattern : type (met één dubbele punt).
  • Opgelost tijdens het compileren : geeft typegegevens aan de typecontrole.
  • Voert geen runtimetypetests uit.
  • Wordt gebruikt voor typedeductie en om de compiler te begeleiden.

Typ testpatroon

Het typetestpatroon wordt gebruikt om de invoer tijdens runtime te vergelijken met een type. Als het invoertype overeenkomt met (of een afgeleid type) van het type dat is opgegeven in het patroon, slaagt de overeenkomst.

Een typetestpatroon maakt gebruik van de syntaxis :? type en voert runtimetypecontrole uit, vergelijkbaar met de is operatoren as in C#. Met dit patroon wordt getest of een waarde van een specifiek type is tijdens het uitvoeren van het programma, waardoor het handig is bij het werken met overnamehiërarchieën of interface-implementaties.

In het volgende voorbeeld ziet u het typetestpatroon:

open System.Windows.Forms

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

Als u alleen controleert of een id van een bepaald afgeleid type is, hebt u het as identifier deel van het patroon niet nodig, zoals wordt weergegeven in het volgende voorbeeld:

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

Belangrijkste kenmerken:

  • Gebruikt de syntaxis :? type of :? type as identifier (met een vraagteken).
  • Opgelost tijdens runtime : voert de daadwerkelijke typecontrole uit tijdens de uitvoering.
  • Test of een waarde een exemplaar is van een specifiek type of de afgeleide typen.
  • Vaak gebruikt met overnamehiërarchieën en polymorfische typen.
  • Vergelijkbaar met de operator of as operator van is C#.

Contrasterende typeaantekeningen en typetestpatronen

Hoewel beide patronen betrekking hebben op typen, dienen ze zeer verschillende doeleinden:

Feature Type Annotatiepatroon (pattern : type) Type testpatroon (:? type)
Syntax Enkele dubbele punt: a : int Dubbele punt met vraagteken: :? Button
Wanneer opgelost Compilatietijd Runtime
Purpose Hulplijnen type deductie Test het werkelijke type waarde
Gebruiksscenario De compiler helpen inzicht te hebben in typen Runtimetypen in overnamehiërarchieën controleren
Equivalent in C# Aantekeningen typen in schakelpatronen is of as operators

In het volgende voorbeeld ziet u de verschillen:

// Type annotation pattern - compile time
let detect1 x =
    match x with
    | 1 -> printfn "Found a 1!"
    | (var1 : int) -> printfn "%d" var1
// The ': int' tells the compiler var1 is an int
// Everything is resolved at compile time

// Type test pattern - runtime
type A() = class end
type B() = inherit A()

let test (a: A) =
    match a with
    | :? B -> printfn "Runtime check: it's a B"
    | _ -> printfn "Runtime check: it's not a B"
// The ':? B' performs a runtime type check
// The actual type is tested during execution

Nulpatroon

Het null-patroon komt overeen met de null-waarde die kan worden weergegeven wanneer u met typen werkt die een null-waarde toestaan. Null-patronen worden vaak gebruikt bij het samenwerken met .NET Framework-code. De retourwaarde van een .NET-API kan bijvoorbeeld de invoer zijn voor een match-expressie. U kunt de programmastroom beheren op basis van of de retourwaarde null is en ook op andere kenmerken van de geretourneerde waarde. U kunt het null-patroon gebruiken om te voorkomen dat null-waarden worden doorgegeven aan de rest van uw programma.

In het volgende voorbeeld worden het null-patroon en het variabelepatroon gebruikt.

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

Null-patroon wordt ook aanbevolen voor de nullbaarheid functionaliteiten van F# 9 :

let len (str: string | null) =
    match str with
    | null -> -1
    | s -> s.Length

Op dezelfde manier kunt u nieuwe nullability-gerelateerde patronen gebruiken:

let len str =       // str is inferred to be `string | null`
    match str with
    | Null -> -1
    | NonNull (s: string) -> s.Length

Naam van patroon

Het nameof patroon komt overeen met een tekenreeks wanneer de waarde gelijk is aan de expressie die volgt op het nameof trefwoord. Dit patroon is met name handig wanneer u tekenreekswaarden wilt vergelijken met de namen van typen, gediscrimineerde samenvoegingscases of andere symbolen in uw code. Het gebruik nameof biedt compilatieveiligheid omdat als u de naam van een symbool wijzigt, het patroon automatisch de nieuwe naam gebruikt.

Een veelvoorkomende use-case is het deserialiseren van gegevens waarbij tekenreekswaarden type- of hoofdletternamen vertegenwoordigen:

type EventType =
    | OrderCreated
    | OrderShipped
    | OrderDelivered

let handleEvent eventName data =
    match eventName with
    | nameof OrderCreated -> printfn "Processing order creation: %s" data
    | nameof OrderShipped -> printfn "Processing order shipment: %s" data
    | nameof OrderDelivered -> printfn "Processing order delivery: %s" data
    | _ -> printfn "Unknown event type: %s" eventName

handleEvent "OrderCreated" "Order #123" // matches first case
handleEvent "OrderShipped" "Order #123" // matches second case

Deze benadering is beter dan het gebruik van letterlijke tekenreeksen (zoals "OrderCreated") omdat:

  • Als u de naam wijzigt OrderCreatedOrderPlaced, wordt het patroon automatisch bijgewerkt.
  • De compiler zorgt ervoor dat het symbool bestaat, waardoor typfouten worden voorkomen.
  • Uw code blijft consistent bij het herstructureren.

U kunt ook gebruiken nameof met parameters:

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

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

Zie de operator nameof voor informatie over wat u een naam kunt geven.

Zie ook