Partilhar via


Padrões Ativos

Os padrões ativos permitem que você defina partições nomeadas que subdividem dados de entrada, para que você possa usar esses nomes em uma expressão de correspondência de padrão da mesma forma que faria para uma união discriminada. Você pode usar padrões ativos para decompor dados de maneira personalizada para cada partição.

Sintaxe

// Active pattern of one choice.
let (|identifier|) [arguments] valueToMatch = expression

// Active Pattern with multiple choices.
// Uses a FSharp.Core.Choice<_,...,_> based on the number of case names. In F#, the limitation n <= 7 applies.
let (|identifier1|identifier2|...|) valueToMatch = expression

// Partial active pattern definition.
// Uses a FSharp.Core.option<_> to represent if the type is satisfied at the call site.
let (|identifier|_|) [arguments] valueToMatch = expression

Observações

Na sintaxe anterior, os identificadores são nomes para partições dos dados de entrada que são representados por argumentos, ou, em outras palavras, nomes para subconjuntos do conjunto de todos os valores dos argumentos. Pode haver até sete partições em uma definição de padrão ativo. A expressão descreve a forma na qual os dados devem ser decompostos. Você pode usar uma definição de padrão ativa para definir as regras para determinar a qual das partições nomeadas pertencem os valores fornecidos como argumentos. Os símbolos (| e |) são referidos como grampos de banana e a função criada por este tipo de ligação let é chamada de reconhecedor ativo.

Como exemplo, considere o seguinte padrão ativo com um argumento.

let (|Even|Odd|) input = if input % 2 = 0 then Even else Odd

Você pode usar o padrão ativo em uma expressão de correspondência de padrão, como no exemplo a seguir.

let TestNumber input =
   match input with
   | Even -> printfn "%d is even" input
   | Odd -> printfn "%d is odd" input

TestNumber 7
TestNumber 11
TestNumber 32

A saída deste programa é a seguinte:

7 is odd
11 is odd
32 is even

Outro uso de padrões ativos é decompor tipos de dados de várias maneiras, como quando os mesmos dados subjacentes têm várias representações possíveis. Por exemplo, um Color objeto pode ser decomposto em uma representação RGB ou uma representação HSB.

open System.Drawing

let (|RGB|) (col : System.Drawing.Color) =
     ( col.R, col.G, col.B )

let (|HSB|) (col : System.Drawing.Color) =
   ( col.GetHue(), col.GetSaturation(), col.GetBrightness() )

let printRGB (col: System.Drawing.Color) =
   match col with
   | RGB(r, g, b) -> printfn " Red: %d Green: %d Blue: %d" r g b

let printHSB (col: System.Drawing.Color) =
   match col with
   | HSB(h, s, b) -> printfn " Hue: %f Saturation: %f Brightness: %f" h s b

let printAll col colorString =
  printfn "%s" colorString
  printRGB col
  printHSB col

printAll Color.Red "Red"
printAll Color.Black "Black"
printAll Color.White "White"
printAll Color.Gray "Gray"
printAll Color.BlanchedAlmond "BlanchedAlmond"

A saída do programa acima é a seguinte:

Red
 Red: 255 Green: 0 Blue: 0
 Hue: 360.000000 Saturation: 1.000000 Brightness: 0.500000
Black
 Red: 0 Green: 0 Blue: 0
 Hue: 0.000000 Saturation: 0.000000 Brightness: 0.000000
White
 Red: 255 Green: 255 Blue: 255
 Hue: 0.000000 Saturation: 0.000000 Brightness: 1.000000
Gray
 Red: 128 Green: 128 Blue: 128
 Hue: 0.000000 Saturation: 0.000000 Brightness: 0.501961
BlanchedAlmond
 Red: 255 Green: 235 Blue: 205
 Hue: 36.000000 Saturation: 1.000000 Brightness: 0.901961

Em combinação, essas duas maneiras de usar padrões ativos permitem particionar e decompor dados apenas na forma apropriada e executar os cálculos apropriados nos dados apropriados na forma mais conveniente para a computação.

As expressões de correspondência de padrões resultantes permitem que os dados sejam escritos de uma maneira conveniente e muito legível, simplificando muito a ramificação potencialmente complexa e o código de análise de dados.

Padrões ativos parciais

Às vezes, você precisa particionar apenas parte do espaço de entrada. Nesse caso, você escreve um conjunto de padrões parciais, cada um dos quais corresponde a algumas entradas, mas não consegue corresponder a outras entradas. Os padrões ativos que nem sempre produzem um valor são chamados de padrões ativos parciais, eles têm um valor de retorno que é um tipo de opção. Para definir um padrão ativo parcial, use um caractere curinga (_) no final da lista de padrões dentro dos clipes de banana. O código a seguir ilustra o uso de um padrão ativo parcial.

let (|Integer|_|) (str: string) =
   let mutable intvalue = 0
   if System.Int32.TryParse(str, &intvalue) then Some(intvalue)
   else None

let (|Float|_|) (str: string) =
   let mutable floatvalue = 0.0
   if System.Double.TryParse(str, &floatvalue) then Some(floatvalue)
   else None

let parseNumeric str =
   match str with
   | Integer i -> printfn "%d : Integer" i
   | Float f -> printfn "%f : Floating point" f
   | _ -> printfn "%s : Not matched." str

parseNumeric "1.1"
parseNumeric "0"
parseNumeric "0.0"
parseNumeric "10"
parseNumeric "Something else"

A saída do exemplo anterior é a seguinte:

1.100000 : Floating point
0 : Integer
0.000000 : Floating point
10 : Integer
Something else : Not matched.

Ao usar padrões ativos parciais, às vezes as escolhas individuais podem ser separadas ou mutuamente exclusivas, mas não precisam ser. No exemplo a seguir, o padrão Quadrado e o padrão Cubo não são disjuntos, porque alguns números são quadrados e cubos, como 64. O programa a seguir usa o padrão AND para combinar os padrões Quadrado e Cubo. Ele imprime todos os inteiros até 1000 que são quadrados e cubos, bem como aqueles que são apenas cubos.

let err = 1.e-10

let isNearlyIntegral (x:float) = abs (x - round(x)) < err

let (|Square|_|) (x : int) =
  if isNearlyIntegral (sqrt (float x)) then Some(x)
  else None

let (|Cube|_|) (x : int) =
  if isNearlyIntegral ((float x) ** ( 1.0 / 3.0)) then Some(x)
  else None

let findSquareCubes x =
   match x with
   | Cube x & Square _ -> printfn "%d is a cube and a square" x
   | Cube x -> printfn "%d is a cube" x
   | _ -> ()
   

[ 1 .. 1000 ] |> List.iter (fun elem -> findSquareCubes elem)

O resultado é o seguinte:

1 is a cube and a square
8 is a cube
27 is a cube
64 is a cube and a square
125 is a cube
216 is a cube
343 is a cube
512 is a cube
729 is a cube and a square
1000 is a cube

Padrões ativos parametrizados

Os padrões ativos sempre usam pelo menos um argumento para o item que está sendo correspondido, mas também podem usar argumentos adicionais, caso em que o padrão ativo parametrizado nome se aplica. Argumentos adicionais permitem que um padrão geral seja especializado. Por exemplo, padrões ativos que usam expressões regulares para analisar cadeias de caracteres geralmente incluem a expressão regular como um parâmetro extra, como no código a seguir, que também usa o padrão Integer ativo parcial definido no exemplo de código anterior. Neste exemplo, cadeias de caracteres que usam expressões regulares para vários formatos de data são dadas para personalizar o padrão ativo geral do ParseRegex. O padrão ativo Integer é usado para converter as cadeias de caracteres correspondentes em inteiros que podem ser passados para o construtor DateTime.

open System.Text.RegularExpressions

// ParseRegex parses a regular expression and returns a list of the strings that match each group in
// the regular expression.
// List.tail is called to eliminate the first element in the list, which is the full matched expression,
// since only the matches for each group are wanted.
let (|ParseRegex|_|) regex str =
   let m = Regex(regex).Match(str)
   if m.Success
   then Some (List.tail [ for x in m.Groups -> x.Value ])
   else None

// Three different date formats are demonstrated here. The first matches two-
// digit dates and the second matches full dates. This code assumes that if a two-digit
// date is provided, it is an abbreviation, not a year in the first century.
let parseDate str =
   match str with
   | ParseRegex "(\d{1,2})/(\d{1,2})/(\d{1,2})$" [Integer m; Integer d; Integer y]
          -> new System.DateTime(y + 2000, m, d)
   | ParseRegex "(\d{1,2})/(\d{1,2})/(\d{3,4})" [Integer m; Integer d; Integer y]
          -> new System.DateTime(y, m, d)
   | ParseRegex "(\d{1,4})-(\d{1,2})-(\d{1,2})" [Integer y; Integer m; Integer d]
          -> new System.DateTime(y, m, d)
   | _ -> new System.DateTime()

let dt1 = parseDate "12/22/08"
let dt2 = parseDate "1/1/2009"
let dt3 = parseDate "2008-1-15"
let dt4 = parseDate "1995-12-28"

printfn "%s %s %s %s" (dt1.ToString()) (dt2.ToString()) (dt3.ToString()) (dt4.ToString())

A saída do código anterior é a seguinte:

12/22/2008 12:00:00 AM 1/1/2009 12:00:00 AM 1/15/2008 12:00:00 AM 12/28/1995 12:00:00 AM

Os padrões ativos não se restringem apenas a expressões de correspondência de padrões, você também pode usá-los em let-bindings.

let (|Default|) onNone value =
    match value with
    | None -> onNone
    | Some e -> e

let greet (Default "random citizen" name) =
    printfn "Hello, %s!" name

greet None
greet (Some "George")

A saída do código anterior é a seguinte:

Hello, random citizen!
Hello, George!

Observe, no entanto, que apenas padrões ativos de caso único podem ser parametrizados.

// A single-case partial active pattern can be parameterized
let (| Foo|_|) s x = if x = s then Some Foo else None
// A multi-case active patterns cannot be parameterized
// let (| Even|Odd|Special |) (s: int) (x: int) = if x = s then Special elif x % 2 = 0 then Even else Odd

Representações de estrutura para padrões ativos parciais

Por padrão, os padrões ativos parciais retornam um option valor, que envolverá uma alocação para o Some valor em uma correspondência bem-sucedida. Como alternativa, você pode usar uma opção de valor como um valor de retorno através do uso do Struct atributo:

open System

[<return: Struct>]
let (|Int|_|) str =
   match Int32.TryParse(str) with
   | (true, n) -> ValueSome n
   | _ -> ValueNone

O atributo deve ser especificado, porque o uso de um retorno struct não é inferido simplesmente alterando o tipo de retorno para ValueOption. Para obter mais informações, consulte RFC FS-1039.

Consulte também