Udostępnij za pośrednictwem


Dopasowywanie wzorców

Wzorce to reguły przekształcania danych wejściowych. Są one używane w języku F# do porównywania danych ze strukturami logicznymi, rozkładania danych na części składowe lub wyodrębniania informacji z danych na różnorodne sposoby.

Uwagi

Wzorce są używane w wielu konstrukcjach językowych, takich jak wyrażenie match. Są one używane podczas przetwarzania argumentów dla funkcji w powiązaniach let, wyrażeniach lambda i w programach obsługi wyjątków skojarzonych z wyrażeniem try...with. Aby uzyskać więcej informacji, zobacz Match Expressions, let Bindings, Lambda Expressions: The fun Keyword, i Exceptions: The try...with Expression.

Na przykład w wyrażeniu match wzorzec jest zgodny z symbolem potoku.

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

Każdy wzorzec działa jako reguła przekształcania danych wejściowych w jakiś sposób. W wyrażeniu match każdy wzorzec jest analizowany z kolei, aby sprawdzić, czy dane wejściowe są zgodne ze wzorcem. Jeśli zostanie znalezione dopasowanie, zostanie wykonane wyrażenie wynikowe. Jeśli dopasowanie nie zostanie znalezione, przetestowana zostanie kolejna reguła wzorca. Opcjonalna część warunku jest objaśniona w sekcji Wyrażenia Dopasowania.

Obsługiwane wzorce przedstawiono w poniższej tabeli. W czasie wykonywania dane wejściowe są testowane względem każdego z poniższych wzorców w kolejności wymienionej w tabeli, a wzorce są stosowane rekursywnie, od pierwszego do ostatniego, gdy pojawiają się w kodzie, a od lewej do prawej dla wzorców w każdym wierszu.

Nazwa Opis Przykład
Stały wzorzec Dowolny literał liczbowy, znakowy lub ciągowy, stała wyliczeniowa lub zdefiniowany identyfikator literałowy 1.0, "test", 30, Color.Red
Wzorzec identyfikatora Wartość przypadku unii dyskryminowanej, etykiety wyjątku lub aktywnego wzorca przypadku Some(x)

Failure(msg)
Zmienny wzorzec identyfikator a
wzorzec as wzorzec jako identyfikator (a, b) as tuple1
WZORZEC OR wzorzec wzorzec1 | wzorzec2 ([h] | [h; _])
wzorzec logiczny AND wzorzec1 & wzorzec2 (a, b) & (_, "test")
Wzorzec wadliwych stron identyfikator :: identyfikator listy h :: t
Wzorzec listy [ pattern_1; ... ; pattern_n ] [ a; b; c ]
Schemat tablicy [| pattern_1; ..; pattern_n |] [| a; b; c |]
Wzorzec ujęty w nawiasy ( wzorzec ) ( a )
Wzorzec krotki ( pattern_1, ... , pattern_n ) ( a, b )
Wzorzec rekordu { identyfikator1 = pattern_1; ... ; identifier_n = pattern_n } { Name = name; }
Wzorzec z symbolami wieloznacznymi _ _
Wzorzec wraz z adnotacją typu wzorzec : typ a : int
Wzorzec testu typu :? wpisz [ jako identyfikator ] :? System.DateTime as dt
Wzorzec o wartości null zero null
Wzorzec nazwy nazwa wyrażenia nameof str

Wzorce stałe

Wzorce stałe to literały liczbowe, znakowe i ciągowe oraz stałe wyliczenia (z uwzględnioną nazwą typu wyliczenia). Wyrażenie match, które ma tylko stałe wzorce, można porównać do instrukcji case w innych językach. Dane wejściowe są porównywane z wartością stałą, a wzorzec jest zgodny, jeśli wartości są równe. Typ literału musi być zgodny z typem danych wejściowych.

W poniższym przykładzie pokazano użycie wzorców literałów, wzorca zmiennej oraz operatora 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

Innym przykładem wzorca literału jest wzorzec oparty na stałych wyliczeniowych. Podczas używania stałych wyliczenia należy określić nazwę typu wyliczenia.

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

Wzorce identyfikatorów

Jeśli wzorzec jest ciągiem znaków, który stanowi prawidłowy identyfikator, formularz identyfikatora określa sposób dopasowania wzorca. Jeśli identyfikator jest dłuższy niż pojedynczy znak i zaczyna się od znaku wielkiej litery, kompilator próbuje dopasować element do wzorca identyfikatora. Identyfikator tego wzorca może być wartością oznaczoną atrybutem Literal, przypadkiem dyskryminowanego związku, identyfikatorem wyjątku lub przypadkiem aktywnego wzorca. Jeśli nie zostanie znaleziony pasujący identyfikator, dopasowanie nie powiedzie się, a następna reguła wzorca, wzorzec zmiennej, zostanie porównana z danymi wejściowymi.

Wzorce unii dyskryminowanej mogą być prostymi przypadkami z nazwą; mogą też mieć wartość albo krotkę zawierającą wiele wartości. Jeśli istnieje wartość, musisz określić identyfikator wartości. W przypadku krotki należy podać wzorzec krotki z identyfikatorem dla każdego elementu krotki lub identyfikatora o nazwie pola dla co najmniej jednego nazwanego pola unii. Zobacz przykłady kodu w tej sekcji, aby zapoznać się z przykładami.

Typ option jest związkiem dyskryminowanym, który ma dwa przypadki, Some i None. Jeden przypadek (Some) ma wartość, ale drugi (None) jest tylko nazwanym przypadkiem. W związku z tym Some musi mieć zmienną dla wartości skojarzonej z przypadkiem Some, ale None musi zostać wyświetlony samodzielnie. W poniższym kodzie zmienna var1 otrzymuje wartość uzyskaną przez dopasowanie do przypadku Some.

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

W poniższym przykładzie związek dyskryminowany PersonName zawiera kombinację ciągów i znaków reprezentujących możliwe formy nazw. Przypadki związków dyskryminowanych są FirstOnly, LastOnlyi 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

W przypadku związków dyskryminowanych, które mają nazwane pola, należy użyć znaku równości (=), aby wyodrębnić wartość nazwanego pola. Rozważmy na przykład dyskryminowaną unię z deklaracją podobną do poniższej.

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

Nazwane pola można użyć w wyrażeniu dopasowania wzorca w następujący sposób.

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

Użycie nazwanego pola jest opcjonalne, więc w poprzednim przykładzie zarówno Circle(r), jak i Circle(radius = r) mają taki sam efekt.

Podczas określania wielu pól należy użyć średnika (;) jako separatora.

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

Aktywne wzorce umożliwiają definiowanie bardziej złożonego dopasowania niestandardowych wzorców. Aby uzyskać więcej informacji na temat aktywnych wzorców, zobacz Aktywne wzorce.

Przypadek, w którym identyfikator jest wyjątkiem, jest używany w dopasowywaniu wzorców w kontekście procedur obsługi wyjątków. Aby uzyskać informacje o dopasowywaniu wzorców w obsłudze wyjątków, zobacz część Wyjątki: wyrażenie try...with.

Wzorce zmiennych

Wzorzec zmiennej przypisuje dopasowaną wartość do nazwy zmiennej, która jest następnie dostępna do użycia w wyrażeniu wykonawczym po prawej stronie symbolu ->. Sam wzorzec zmiennej jest zgodny z dowolnymi danymi wejściowymi, ale wzorce zmiennych często pojawiają się w innych wzorcach, co umożliwia bardziej złożone struktury, takie jak krotki i tablice, które mają być rozłożone na zmienne.

W poniższym przykładzie pokazano wzorzec zmiennej we wzorcu krotki.

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 wzorzec

Wzorzec as jest wzorcem, który ma dołączony do niego klauzulę as. Klauzula as wiąże dopasowaną wartość z nazwą, która może być używana w wykonawczym wyrażeniu match, lub, w przypadku gdy ten wzorzec jest używany w wiązaniu let, nazwa jest dodawana jako lokalne wiązanie.

W poniższym przykładzie użyto wzorca as.

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

WZORZEC OR

Wzorzec OR jest używany, gdy dane wejściowe mogą być zgodne z wieloma wzorcami i chcesz wykonać ten sam kod w wyniku. Typy obu stron wzorca OR muszą być zgodne.

W poniższym przykładzie pokazano wzorzec 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)

WZORZEC AND

Wzorzec AND wymaga dopasowania danych wejściowych do dwóch wzorców. Typy obu stron wzorca AND muszą być zgodne.

Poniższy przykład jest podobny do detectZeroTuple, pokazany w sekcji dotyczącej wzorca krotki w dalszej części tego tematu, ale w tym przypadku zarówno var1, jak i var2 są uzyskiwane jako wartości przy użyciu wzorca AND.

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)

Wzorzec cons (jeśli w kontekście programowania, zostaw "cons" jako techniczny termin)

Wzorzec cons służy do rozdzielania listy na pierwszy element, head, i listę zawierającą pozostałe elementy, 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

Wzorzec listy

Wzorzec listy umożliwia rozłożenie list na wiele elementów. Wzorzec listy może być zgodny tylko z listami określonej liczby elementów.

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

Wzorzec tablicowy

Wzorzec tablicy przypomina wzorzec listy i może służyć do rozkładania tablic o określonej długości.

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

Schemat nawiasów

Nawiasy można grupować wokół wzorców, aby osiągnąć żądaną asocjatywność. W poniższym przykładzie nawiasy służą do kontrolowania asocjacji między wzorcem AND a wzorcem cons.

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

Wzorzec krotki

Wzorzec krotki pasuje do danych wejściowych w formie krotki i umożliwia rozłożenie krotki na elementy składowe przy użyciu zmiennych pasujących do wzorca dla każdej pozycji w krotce.

W poniższym przykładzie pokazano wzorzec krotki, a także używa wzorców literałów, wzorców zmiennych i wzorca symboli wieloznacznych.

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)

Wzorzec rekordu

Wzorzec rekordu służy do dekompilowania rekordów w celu wyodrębnienia wartości pól. Wzorzec nie musi odwoływać się do wszystkich pól rekordu; wszystkie pominięte pola po prostu nie uczestniczą w dopasowywaniu i nie są wyodrębniane.

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

Wzorzec z symbolami wieloznacznymi

Wzorzec z symbolami wieloznacznymi jest reprezentowany przez znak podkreślenia (_) i pasuje do wszystkich danych wejściowych, podobnie jak wzorzec zmiennej, z tą różnicą, że dane wejściowe są odrzucane zamiast przypisywane do zmiennej. Wzorzec symbolu wieloznacznego jest często używany w innych wzorcach jako symbol zastępczy dla wartości, które nie są potrzebne w wyrażeniu po prawej stronie symbolu ->. Wzorzec z symbolami wieloznacznymi jest również często używany na końcu listy wzorców, aby dopasować je do wszelkich niezgodnych danych wejściowych. Wzorzec z symbolami wieloznacznymi przedstawiono w wielu przykładach kodu w tym temacie. Zobacz poprzedni kod, aby zapoznać się z jednym przykładem.

Wzorce, które mają adnotacje typu

Wzorce mogą mieć adnotacje typu. Zachowują się one jak inne adnotacje typu i prowadzą wnioskowanie, podobnie jak inne adnotacje typu. Nawiasy są wymagane wokół adnotacji typu we wzorcach. Poniższy kod przedstawia schemat z adnotacją typu.

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

Wzorzec testu typu

Wzorzec testu typu służy do dopasowywania danych wejściowych do typu. Jeśli typ danych wejściowych jest dopasowaniem (lub typem pochodnym) typu określonego we wzorcu, dopasowanie powiedzie się.

W poniższym przykładzie pokazano wzorzec 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."
    | _ -> ()

Jeśli sprawdzasz tylko, czy identyfikator jest określonym typem pochodnym, nie potrzebujesz części wzorca as identifier, jak pokazano w poniższym przykładzie.

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

Wzorzec o wartości null

Wzorzec wartości null jest zgodny z wartością null, która może być wyświetlana podczas pracy z typami, które zezwalają na wartość null. Wzorce o wartości null są często używane podczas współdziałania z kodem programu .NET Framework. Na przykład zwracana wartość interfejsu API platformy .NET może stanowić dane wejściowe wyrażenia match. Przepływ programu można kontrolować na podstawie tego, czy wartość zwracana ma wartość null, a także inne cechy zwracanej wartości. Możesz użyć wzorca null, aby zapobiec propagacji wartości null do pozostałej części programu.

W poniższym przykładzie użyto wzorca null i wzorca zmiennej.

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

Wzorzec wartości null jest również zalecany w przypadku możliwości wartości null języka F# 9:

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

Podobnie można użyć nowych specjalnych wzorców związanych z przechowywaniem wartości null :

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

Wzorzec nameof

Wzorzec nameof pasuje do ciągu, gdy jego wartość jest równa wyrażeniu, które następuje po słowie kluczowym nameof. na przykład:

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

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

Aby uzyskać informacje, co może mieć nazwę, zobacz operator nameof.

Zobacz też