Sdílet prostřednictvím


Rozpoznávání vzorů

Vzory jsou pravidla pro transformaci vstupních dat. Používají se v rámci jazyka F# k porovnání dat s logickou strukturou nebo strukturami, rozdělení dat do základních částí nebo k extrakci informací z dat různými způsoby.

Poznámky

Vzory se používají v mnoha konstruktorech jazyka, jako je například výraz match. Používají se při zpracování argumentů pro funkce v let vazbách, výrazech lambda a v obslužných rutinách výjimek přidružených k výrazu try...with. Další informace naleznete v tématu match expressions, let bindings, lambda Expressions: The fun Keyworda exceptions: The try...with Expression.

Například ve výrazu match je vzor tím, co následuje za symbolem svislé roury.

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

Každý vzor funguje jako pravidlo pro transformaci vstupu nějakým způsobem. Ve výrazu match se každý vzor prochází, aby se zjistilo, zda jsou vstupní data ve shodě se vzorem. Pokud se najde shoda, provede se výsledný výraz. Pokud se shoda nenajde, otestuje se další pravidlo vzoru. Volitelná podmínka část je vysvětlena v části Výrazy pro shodu.

Podporované vzory jsou uvedené v následující tabulce. V době běhu se vstup testuje pro každý z následujících vzorů v pořadí uvedeném v tabulce a vzory se použijí rekurzivně, od prvního po poslední, jakmile se zobrazí v kódu, a zleva doprava pro vzory na každém řádku.

Jméno Popis Příklad
Konstantní vzor Libovolný číselný, znakový nebo řetězcový literál, konstanta výčtu nebo definovaný identifikátor literálu 1.0, "test", , 30Color.Red
Vzor identifikátoru Hodnota případu diskriminované unie, značka výjimky nebo případ aktivního vzoru Some(x)

Failure(msg)
Vzor proměnné identifikátor a
vzor as jako vzor jako identifikátor (a, b) as tuple1
Vzorec OR vzor1 | vzor2 ([h] | [h; _])
Vzor AND vzor1 & vzor2 (a, b) & (_, "test")
Vzor nevýhod identifikátor :: identifikátor seznamu h :: t
Vzor seznamu [ pattern_1; ... ; pattern_n ] [ a; b; c ]
Vzor pole [| pattern_1; ..; pattern_n |] [| a; b; c |]
Vzor v závorkách ( vzor ) ( a )
Vzor n-tice ( vzor_1, ... , vzor_n ) ( a, b )
Vzor záznamu { identifier1 = pattern_1; ... ; = identifier_npattern_n } { Name = name; }
Vzor se zástupnými znaky _ _
Vzor společně s poznámkami k typu vzor : typ a : int
Model testu typu :? typ [ jako identifikátor ] :? System.DateTime as dt
Nulový vzor null null
Název vzoru název výrazu nameof str

Konstantní vzory

Konstantní vzory jsou číselné, znakové a řetězcové literály, výčtové konstanty (včetně názvu typu výčtu). Výraz match, který má pouze konstantní vzory, lze porovnat s příkazem case v jiných jazycích. Vstup je porovnán s doslovnou hodnotou a vzor se shoduje, pokud jsou hodnoty stejné. Typ literálu musí být kompatibilní s typem vstupu.

Následující příklad ukazuje použití literálových vzorů a také používá proměnný vzor a vzor OR.

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

Dalším příkladem literálového vzoru je model založený na konstantách výčtu. Při použití konstant výčtu je nutné zadat název typu výčtu.

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

Vzory identifikátorů

Pokud je vzor řetězcem znaků, které tvoří platný identifikátor, určuje formulář identifikátoru, jak se vzor shoduje. Pokud je identifikátor delší než jeden znak a začíná velkým znakem, kompilátor se pokusí vytvořit shodu se vzorem identifikátoru. Identifikátor tohoto vzoru může být hodnota označená atributem Literal, diskriminovaný unijní případ, identifikátor výjimky nebo aktivní vzorový případ. Pokud se nenajde žádný odpovídající identifikátor, shoda selže a další pravidlo vzoru, proměnlivý vzor, se porovná se vstupem.

Diskriminované vzory sjednocení mohou být jednoduché pojmenované případy nebo mohou mít hodnotu nebo řazenou kolekci členů obsahující více hodnot. Pokud je hodnota, musíte zadat identifikátor hodnoty. V případě n-tice musíte dodat vzor n-tice s identifikátorem pro každý prvek n-tice nebo identifikátor s názvem pole pro jedno či více pojmenovaných polí sjednocení. Příklady najdete v příkladech kódu v této části.

Typ option je diskriminovaná unie, která má dva případy, Some a None. Jeden případ (Some) má hodnotu, ale druhý (None) je jen pojmenovaný případ. Proto Some musí mít proměnnou pro hodnotu přidruženou k Some případu, ale None musí být zobrazena samostatně. V následujícím kódu je proměnné var1 přiřazena hodnota získaná při odpovídající případu Some.

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

V následujícím příkladu diskriminovaná unie PersonName obsahuje kombinaci řetězců a znaků, které představují možné formy jmen. Případy diskriminované unie jsou FirstOnly, LastOnlya 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

Pro diskriminovaná sjednocení, která mají pojmenovaná pole, použijete znak rovnosti (=) pro extrakci hodnoty z pojmenovaného pole. Představte si například diskriminované sjednocení s deklarací jako je následující.

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

Pojmenovaná pole můžete použít ve vzorovém shodném výrazu následujícím způsobem.

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

Použití pojmenovaného pole je volitelné, takže v předchozím příkladu mají oba Circle(r) i Circle(radius = r) stejný účinek.

Při zadávání více polí použijte středník (;) jako oddělovač.

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

Aktivní vzory vám umožňují definovat složitější vlastní porovnávání vzorů. Další informace o aktivních vzorech naleznete v tématu aktivní vzory.

Případ, ve kterém je identifikátor výjimkou, se používá ve vzorovém porovnávání v kontextu obslužných rutin výjimek. Informace o porovnávání vzorů při zpracování výjimek naleznete v tématu Výjimky: Výraz try...with.

Vzory proměnných

Vzor proměnné přiřadí hodnotu, která se shoduje s názvem proměnné, která je pak k dispozici pro použití ve výrazu spuštění napravo od symbolu ->. Samotný vzor proměnných odpovídá jakémukoli vstupu, ale vzory proměnných se často objevují v jiných vzorech, a proto umožňují složitější struktury, jako jsou řazené kolekce členů a pole, rozdělit do proměnných.

Následující příklad ukazuje vzor proměnné v rámci vzoru n-tice.

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)

jako vzor

Vzor as je vzor, který má připojenou klauzuli as. Klauzule as sváže shodnou hodnotu s názvem, který lze použít ve výrazu provádění výrazu match nebo v případě, že se tento vzor používá v vazbě let, název se přidá jako vazba k místnímu oboru.

Následující příklad používá vzor as.

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

OR vzor

Vzor OR se používá, když vstupní data můžou odpovídat více vzorům a chcete spustit stejný kód jako výsledek. Typy obou stran vzoru OR musí být kompatibilní.

Následující příklad ukazuje vzor OR.

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)

Vzor AND

Model AND vyžaduje, aby vstup odpovídal dvěma vzorům. Typy obou stran vzoru AND musí být kompatibilní.

Následující příklad je podobný jako detectZeroTuple, který je zobrazen v části Vzor n-tice později v tomto tématu, ale zde jsou var1 i var2 získány jako hodnoty pomocí AND vzoru.

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)

Vzor nevýhody

Vzorec cons se používá k rozkladu seznamu na první prvek, hlavu, a seznam, který obsahuje zbývající prvky, ocas.

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

Můžete také zřetězit několik vzorů záporů, aby odpovídaly seznamům, které začínají konkrétními sekvencemi prvků.

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']

Vzor seznamu

Vzor seznamu umožňuje rozdělení seznamů na několik prvků. Samotný vzor seznamu se může shodovat pouze se seznamy určitého počtu prvků.

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

Vzor pole

Maticový vzor se podobá vzoru seznamu a lze ho použít k dekompilování polí s konkrétní délkou.

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

Vzor závorek

Závorky lze seskupit podle vzorů, aby bylo dosaženo požadované asociativity. V následujícím příkladu se závorky používají k řízení asociativity mezi vzorem AND a vzorem záporů.

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

Vzor n-tice

Vzor n-tice odpovídá vstupu ve formě n-tice a umožňuje rozdělení n-tice na její složky pomocí proměnných vzoru pro každou pozici v rámci n-tice.

Následující příklad ukazuje vzor n-tice a také používá literální vzory, vzory proměnných a zástupný vzor.

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)

Vzor záznamu

Vzor záznamu slouží k dekompose záznamů pro extrahování hodnot polí. Vzor nemusí odkazovat na všechna pole záznamu; všechna vynechaná pole se neúčastní shody a nejsou extrahována.

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

Vzor zástupných znaků

Vzor zástupného znaku je reprezentován znakem podtržítka (_) a odpovídá jakémukoli vstupu, stejně jako vzor proměnné, s tím rozdílem, že vstup se místo přiřazení proměnné zahodí. Vzor se zástupnými znaky se často používá v jiných vzorech jako zástupce pro hodnoty, které nejsou ve výrazu napravo od symbolu -> potřeba. Vzor se zástupnými znamény se také často používá na konci seznamu vzorů, aby odpovídal jakémukoli chybějícímu vstupu. Vzor zástupných znaků je ukázaný v mnoha příkladech kódu v tomto tématu. Příklad najdete v předchozím kódu.

Následující kód ukazuje několik dalších použití zástupného znaku:

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

Vzory s typovými anotacemi

Vzory můžou obsahovat poznámky k typu. Chovají se jako jiné poznámky k typům a vedou odvození jako jiné poznámky k typům. U poznámek typu ve vzorech se vyžadují závorky.

Vzor s poznámkou typu používá syntaxi pattern : type a poskytuje informace o typu kompilace pro kontrolu typů. Toto je čistě statická poznámka typu, která pomáhá s odvozováním typu – neprovádí žádnou kontrolu typů za běhu ani převod. Kompilátor tyto informace používá během kompilace k určení typu proměnné vzoru.

Následující kód ukazuje vzor, který má poznámku typu:

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

V tomto příkladu říká kompilátoru, (var1 : int) že var1 je typu int. Tento problém se vyřeší v době kompilace a vygenerovaný kód považuje var1 celé číslo ve výrazu shody. Tento vzor bude odpovídat libovolné celočíselné hodnotě a sváže ji s var1.

Klíčové charakteristiky:

  • Používá syntaxi pattern : type (s jednou dvojtečku).
  • Vyřešeno v době kompilace – poskytuje kontrole typů informace o typu.
  • Neprovádí testování typů modulu runtime.
  • Používá se k odvozování typů a k vedení kompilátoru.

Model testu typu

Vzor testu typu se používá ke spárování vstupu s typem za běhu. Pokud je vstupní typ shodný s typem (nebo odvozeným typem) typu zadaného v vzoru, shoda bude úspěšná.

Vzor testu typu používá syntaxi :? type a provádí kontrolu typů modulu runtime, podobně jako is operátory v as jazyce C#. Tento vzor testuje, jestli je hodnota určitého typu během provádění programu, což je užitečné při práci s hierarchiemi dědičnosti nebo implementacemi rozhraní.

Následující příklad ukazuje vzor testu typu:

open System.Windows.Forms

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

Pokud kontrolujete, zda je identifikátor konkrétního odvozeného typu, nepotřebujete část vzoru as identifier, jak je ukázáno v následujícím příkladu:

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

Klíčové charakteristiky:

  • Používá syntaxi :? type nebo :? type as identifier (se otazníkem).
  • Vyřešeno za běhu – provádí skutečnou kontrolu typů během provádění.
  • Testuje, jestli je hodnota instance určitého typu nebo jeho odvozených typů.
  • Běžně se používá s hierarchiemi dědičnosti a polymorfními typy.
  • Podobá se operátoru nebo as operátoru jazyka is C#.

Kontrastní poznámky k typům a vzory testů typů

I když oba vzory zahrnují typy, slouží velmi různým účelům:

Vlastnost Model anotace typu (pattern : type) Model testování typů (:? type)
Syntax Jedno dvojtečka: a : int Dvojtečka s otazníkem: :? Button
Při řešení Čas kompilace Runtime
Purpose Odvození typu vodítka Testuje skutečný typ hodnoty.
Případ použití Pomoc kompilátoru pochopit typy Kontrola typů modulu runtime v hierarchiích dědičnosti
Ekvivalent v jazyce C# Psaní poznámek v vzorech přepínačů is operátory nebo as operátory

Následující příklad ukazuje rozdíly:

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

Nullový vzor

Vzor null odpovídá hodnotě null, která se může zobrazit při práci s typy, které umožňují hodnotu null. Vzory null se často používají při spolupráci s kódem rozhraní .NET Framework. Návratovou hodnotou rozhraní .NET API může být například vstup do výrazu match. Tok programu můžete řídit na základě toho, jestli je vrácená hodnota null, a také na dalších vlastnostech vrácené hodnoty. Pomocí vzoru null můžete zabránit šíření hodnot null do zbytku programu.

Následující příklad používá vzor null a vzor proměnné.

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

Doporučuje se také vzor null pro funkce nulovatelnosti ve F# 9 .

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

Podobně můžete použít nové vzory související s vyhrazenou hodnotou null :

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

Název vzoru

Vzor nameof odpovídá řetězci, pokud se jeho hodnota rovná výrazu, který následuje za klíčovým slovem nameof. Tento vzor je užitečný zejména v případě, že potřebujete shodovat řetězcové hodnoty s názvy typů, diskriminovanými případy sjednocení nebo jinými symboly v kódu. Použití nameof poskytuje bezpečnost v době kompilace, protože pokud přejmenujete symbol, vzor automaticky použije nový název.

Běžným případem použití je deserializace dat, kde řetězcové hodnoty představují názvy typů nebo malých písmen:

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

Tento přístup je lepší než použití řetězcových literálů (například "OrderCreated") z následujících důvodů:

  • Pokud přejmenujete OrderCreated na OrderPlaced, vzor se automaticky aktualizuje.
  • Kompilátor zajišťuje, že symbol existuje a brání překlepům.
  • Váš kód zůstává konzistentní při refaktoringu.

Můžete také použít nameof s parametry:

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

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

Informace o tom, co lze pojmenovat, najdete u operátoru nameof.

Viz také