Partilhar via


Correspondência de padrões

Os padrões são regras para transformar dados de entrada. Eles são usados em todo o F# para comparar dados com uma estrutura ou estruturas lógicas, decompor dados em partes constituintes ou extrair informações de dados de várias maneiras.

Comentários

Os padrões são usados em muitas construções de linguagem, como a expressão match. Eles são usados quando você está processando argumentos para funções em associações de let, expressões lambda e nos manipuladores de exceção associados à expressão try...with. Para obter mais informações, consulte Match Expressions, let Bindings, Lambda Expressions: The fun Keyworde Exceptions: The try...with Expression.

Por exemplo, na expressão match, o padrão é o que segue o símbolo do tubo.

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

Cada padrão atua como uma regra para transformar a entrada de alguma forma. Na expressão match, cada padrão é examinado por sua vez para ver se os dados de entrada são compatíveis com o padrão. Se uma correspondência for encontrada, a expressão de resultado será executada. Se uma correspondência não for encontrada, a próxima regra de padrão será testada. O opcional quando condição parte é explicado em Expressões de correspondência.

Os padrões suportados são mostrados na tabela a seguir. Em tempo de execução, a entrada é testada em relação a cada um dos seguintes padrões na ordem listada na tabela, e os padrões são aplicados recursivamente, do primeiro ao último conforme aparecem no código, e da esquerda para a direita para os padrões em cada linha.

Nome Descrição Exemplo
Padrão constante Qualquer literal numérico, de caractere ou de cadeia de caracteres, uma constante de enumeração ou um identificador literal definido 1.0, "test", 30, Color.Red
Padrão identificador Um valor de caso de uma união discriminada, um rótulo de exceção ou um caso de padrão ativo Some(x)

Failure(msg)
Padrão variável identificador a
as padrão padrão como identificador (a, b) as tuple1
Padrão OR padrão1 | padrão2 ([h] | [h; _])
Padrão AND pattern1 & pattern2 (a, b) & (_, "test")
Padrão Cons identificador :: identificador de lista h :: t
Padrão de lista [ pattern_1; ... ; pattern_n ] [ a; b; c ]
Padrão de array [| pattern_1; ..; pattern_n |] [| a; b; c |]
Padrão entre parênteses ( padrão ) ( a )
Padrão de tupla ( pattern_1, ... , pattern_n ) ( a, b )
Padrão de registro { identificador1 = pattern_1; ... ; = identifier_npattern_n } { Name = name; }
Padrão curinga _ _
Padrão com anotação de tipo padrão : tipo a : int
Tipo de padrão de teste :? digite [ como identificador ] :? System.DateTime as dt
Padrão nulo null null
Nome do padrão nome de expr nameof str

Padrões constantes

Padrões constantes são literais numéricos, de caractere e de cadeia de caracteres, constantes de enumeração (com o nome do tipo de enumeração incluído). Uma expressão match que tem apenas padrões constantes pode ser comparada a uma declaração de caso em outros idiomas. A entrada é comparada com o valor literal e o padrão corresponde se os valores forem iguais. O tipo do literal deve ser compatível com o tipo da entrada.

O exemplo a seguir demonstra o uso de padrões literais e também usa um padrão variável e um padrão 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

Outro exemplo de um padrão literal é um padrão baseado em constantes de enumeração. Você deve especificar o nome do tipo de enumeração ao usar constantes de enumeração.

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

Padrões de identificador

Se o padrão for uma cadeia de caracteres que forma um identificador válido, a forma do identificador determinará como o padrão será correspondido. Se o identificador for maior que um único caractere e começar com um caractere maiúsculo, o compilador tentará fazer uma correspondência com o padrão de identificador. O identificador para esse padrão pode ser um valor marcado com o atributo Literal, um caso de união discriminado, um identificador de exceção ou um caso de padrão ativo. Se nenhum identificador correspondente for encontrado, a correspondência falhará e a próxima regra de padrão, o padrão variável, será comparada à entrada.

Os padrões de união discriminados podem ser casos nomeados simples ou podem ter um valor, ou uma tupla que contenha vários valores. Se houver um valor, você deverá especificar um identificador para o valor. No caso de uma tupla, você deve fornecer um padrão de tupla com um identificador para cada elemento da tupla ou um identificador com um nome de campo para um ou mais campos de união nomeados. Veja os exemplos de código nesta seção para obter exemplos.

O tipo option é uma união discriminada que tem dois casos: Some e None. Um caso (Some) tem um valor, mas o outro (None) é apenas um caso nomeado. Portanto, Some precisa ter uma variável para o valor associado ao Some caso, mas None deve aparecer por si só. No código a seguir, a variável var1 recebe o valor que é obtido por correspondência com o caso Some.

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

No exemplo a seguir, a união discriminada PersonName contém uma mistura de cadeias e caracteres que representam possíveis formas de nomes. Os casos da união discriminada são FirstOnly, LastOnlye 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

Para uniões discriminadas que têm campos nomeados, use o sinal de igual (=) para extrair o valor de um campo nomeado. Por exemplo, considere uma união discriminada com uma declaração como a seguinte.

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

Você pode usar os campos nomeados em uma expressão de correspondência de padrão da seguinte maneira.

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

O uso do campo nomeado é opcional, portanto, no exemplo anterior, tanto Circle(r) quanto Circle(radius = r) têm o mesmo efeito.

Ao especificar vários campos, use o ponto-e-vírgula (;) como separador.

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

Os padrões ativos permitem definir uma correspondência de padrões personalizados mais complexa. Para obter mais informações sobre padrões ativos, consulte Ative Patterns.

O caso em que o identificador é uma exceção é usado na correspondência de padrões no contexto de manipuladores de exceção. Para obter informações sobre a correspondência de padrões no tratamento de exceções, consulte Exceptions: The try...with Expression.

Padrões variáveis

O padrão de variável atribui o valor que corresponde a um nome de variável, que fica disponível para uso na expressão de execução à direita do símbolo ->. Um padrão de variável por si só corresponde a qualquer entrada, mas os padrões variáveis geralmente aparecem dentro de outros padrões, permitindo que estruturas mais complexas, como tuplas e matrizes, sejam decompostas em variáveis.

O exemplo a seguir demonstra um padrão variável dentro de um padrão de tupla.

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)

como padrão

O padrão as é um padrão que tem uma cláusula as anexada a ele. A cláusula as vincula o valor correspondente a um nome que pode ser usado na expressão de execução de uma expressão match ou, no caso em que esse padrão é usado em uma associação let, o nome é adicionado como uma associação ao escopo local.

O exemplo a seguir usa um padrão as.

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

Padrão OR

O padrão OR é usado quando os dados de entrada podem corresponder a vários padrões e você deseja executar o mesmo código como resultado. Os tipos de ambos os lados do padrão OR devem ser compatíveis.

O exemplo a seguir demonstra o padrão 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)

Padrão AND

O padrão AND requer que a entrada corresponda a dois padrões. Os tipos de ambos os lados do padrão AND devem ser compatíveis.

O exemplo a seguir é como detectZeroTuple mostrado na seção Tuple Pattern mais adiante neste tópico, mas aqui var1 e var2 são obtidos como valores usando o padrão 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)

Cons Padrão

O padrão cons é utilizado para decompor uma lista no primeiro elemento, o head, e numa lista que contém os elementos restantes, a 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

Você também pode encadear vários padrões de contras para corresponder a listas que começam com sequências específicas de elementos.

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

Padrão de lista

O padrão de lista permite que as listas sejam decompostas em vários elementos. O próprio padrão de lista pode corresponder apenas a listas de um número específico de elementos.

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

Padrão de Array

O padrão de matriz se assemelha ao padrão de lista e pode ser usado para decompor matrizes de um comprimento específico.

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

Padrão entre parênteses

Os parênteses podem ser agrupados em torno de padrões para alcançar a associatividade desejada. No exemplo a seguir, parênteses são usados para controlar a associatividade entre um padrão AND e um padrão contras.

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

Padrão de tupla

O padrão de tupla corresponde à entrada na forma de tupla e permite que a tupla seja decomposta em seus elementos constituintes usando variáveis de correspondência de padrão para cada posição na tupla.

O exemplo a seguir demonstra o padrão de tupla e também usa padrões literais, padrões variáveis e o padrão curinga.

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)

Padrão de registro

O padrão de registro é usado para decompor registros para extrair os valores dos campos. O padrão não tem de fazer referência a todos os campos do registo; todos os campos omitidos simplesmente não participam da correspondência e não são extraídos.

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

Padrão curinga

O padrão curinga é representado pelo caractere sublinhado (_) e corresponde a qualquer entrada, assim como o padrão da variável, exceto que a entrada é descartada em vez de atribuída a uma variável. O padrão curinga é frequentemente usado dentro de outros padrões como um espaço reservado para valores que não são necessários na expressão à direita do símbolo ->. O padrão curinga também é usado com frequência no final de uma lista de padrões para corresponder a qualquer entrada não correspondida. O padrão curinga é demonstrado em muitos exemplos de código neste tópico. Veja o código anterior para um exemplo.

O código a seguir mostra alguns usos adicionais do padrão curinga:

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

Padrões que têm anotações de tipo

Os padrões podem ter anotações de tipo. Estes se comportam como outras anotações de tipo e guiam a inferência como outras anotações de tipo. Parênteses são necessários em torno de anotações de tipo em padrões.

Um padrão com uma anotação de tipo usa a sintaxe pattern : type e fornece informações de tipo em tempo de compilação para o verificador de tipo. Esta é puramente uma anotação de tipo estático que ajuda com a inferência de tipo - não executa nenhuma verificação ou conversão de tipo de tempo de execução. O compilador usa essas informações durante a compilação para determinar o tipo da variável padrão.

O código a seguir mostra um padrão que tem uma anotação de tipo:

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

Neste exemplo, (var1 : int) diz ao compilador que var1 é do tipo int. Isso é resolvido em tempo de compilação, e o código gerado é var1 tratado como um inteiro em toda a expressão de correspondência. Esse padrão corresponderá a qualquer valor inteiro e o vinculará ao var1.

Principais características:

  • Usa a sintaxe pattern : type (com dois pontos).
  • Resolvido em tempo de compilação - fornece informações de tipo para o verificador de tipo.
  • Não executa testes de tipo de tempo de execução.
  • Usado para inferência de tipo e para guiar o compilador.

Padrão de teste de tipo

O padrão de teste de tipo é usado para corresponder a entrada a um tipo em tempo de execução. Se o tipo de entrada for uma correspondência com (ou um tipo derivado de) o tipo especificado no padrão, a correspondência será bem-sucedida.

Um padrão de teste de tipo usa a sintaxe :? type e executa a verificação de tipo de tempo de execução, semelhante aos is operadores or as em C#. Esse padrão testa se um valor é de um tipo específico durante a execução do programa, tornando-o útil ao trabalhar com hierarquias de herança ou implementações de interface.

O exemplo a seguir demonstra o padrão de teste de tipo:

open System.Windows.Forms

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

Se você estiver verificando apenas se um identificador é de um tipo derivado específico, não precisará da parte as identifier do padrão, conforme mostrado no exemplo a seguir:

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

Principais características:

  • Usa a sintaxe :? type ou :? type as identifier (com um ponto de interrogação).
  • Resolvido em tempo de execução - executa a verificação de tipo real durante a execução.
  • Testa se um valor é uma instância de um tipo específico ou seus tipos derivados.
  • Comumente usado com hierarquias de herança e tipos polimórficos.
  • Semelhante ao operador ou as operador do is C#.

Anotações de tipo contrastantes e padrões de teste de tipo

Embora ambos os padrões envolvam tipos, eles servem propósitos muito diferentes:

Característica Padrão de anotação de tipo (pattern : type) Tipo de padrão de teste (:? type)
Syntax Ponto e vírgula simples: a : int Colón com ponto de interrogação: :? Button
Quando resolvido Tempo de compilação Tempo de execução
Purpose Inferência de tipo de guias Testa o tipo real de valor
Cenário de teste Ajudando o compilador a entender os tipos Verificando tipos de tempo de execução em hierarquias de herança
Equivalente em C# Anotações de tipo em padrões de switch is ou as operadores

O exemplo a seguir demonstra as diferenças:

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

Padrão nulo

O padrão nulo corresponde ao valor nulo que pode aparecer quando você está trabalhando com tipos que permitem um valor nulo. Padrões nulos são freqüentemente usados ao interoperar com o código do .NET Framework. Por exemplo, o valor de retorno de uma API .NET pode ser a entrada para uma expressão match. Você pode controlar o fluxo do programa com base em se o valor de retorno é nulo e também em outras características do valor retornado. Você pode usar o padrão nulo para impedir que valores nulos se propaguem para o resto do programa.

O exemplo a seguir usa o padrão nulo e o padrão variável.

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

O padrão nulo também é recomendado para os recursos de anulabilidade do F# 9 :

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

De forma semelhante, podes usar os novos padrões dedicados relacionados com a anulabilidade :

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

Nome do padrão

O padrão nameof corresponde a uma cadeia de caracteres quando seu valor é igual à expressão que segue a palavra-chave nameof. Esse padrão é particularmente útil quando você precisa fazer a correspondência de valores de cadeia de caracteres com os nomes de tipos, casos de união discriminados ou outros símbolos em seu código. Usar nameof fornece segurança em tempo de compilação porque se você renomear um símbolo, o padrão usará automaticamente o novo nome.

Um caso de uso comum é a desserialização de dados em que os valores de cadeia de caracteres representam nomes de tipo ou caso:

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

Essa abordagem é melhor do que usar literais de cadeia de caracteres (como "OrderCreated") porque:

  • Se você mudar o nome OrderCreated para OrderPlaced, o padrão será atualizado automaticamente.
  • O compilador garante que o símbolo existe, evitando erros de digitação.
  • Seu código permanece consistente ao refatoração.

Você também pode usar nameof com parâmetros:

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

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

Consulte o operador nameof para obter informações sobre o que pode ser nomeado.

Ver também