Compartilhar via


Padrões ativos

Os padrões ativos permitem definir partições nomeadas que subdividem os dados de entrada, para que você possa usar esses nomes em uma expressão de padrões correspondentes da mesma forma que faria para uma união discriminada. Você pode usar padrões ativos para decompor os dados de uma 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

Comentários

Na sintaxe anterior, os identificadores são nomes para partições dos dados de entrada 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 ativa. A expressão descreve a forma em que os dados são decompostos. Você pode usar uma definição de padrão ativo para definir as regras que determinam a qual das partições nomeadas pertencem os valores fornecidos como argumentos. Os símbolos (| e |) são chamados de pipes ativos e a função criada por esse tipo de associação let é chamado de reconhecedor ativo.

Por exemplo, considere o padrão ativo a seguir 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 padrões correspondentes, 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 desse programa é a seguinte:

7 is odd
11 is odd
32 is even

Outro uso de padrões ativos é decompor os tipos de dados de várias maneiras, como quando os mesmos dados subjacentes têm várias representações possíveis. Por exemplo, um objeto Color pode ser decomposto em uma representação RGB ou 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 prática para a computação.

As expressões de padrões correspondentes resultantes permitem que os dados sejam gravados de forma prática, simplificando consideravelmente o código de análise de dados e ramificação possivelmente complexo.

Padrões Ativos Parciais

Às vezes, você precisa particionar apenas parte do espaço de entrada. Nesse caso, você gravará um conjunto de padrões parciais que correspondem a algumas entradas, mas não 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 retornado que é um tipo de opção. Para definir um padrão ativo parcial, use um caractere curinga (_) ao final da lista de padrões dentro dos pipes ativos. 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"

Veja a seguir a saída do exemplo anterior:

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 incoerentes ou mutuamente exclusivas, mas não precisam ser. No exemplo a seguir, o padrão Quadrático e o padrão Cúbico não são incoerentes, pois alguns números são quadráticos e cúbicos, como 64. O programa a seguir usa o padrão AND para combinar os padrões Quadrático e Cúbico. Ele imprime todos os inteiros até 1.000, que são quadráticos e cúbicos, bem como os que são apenas cúbicos.

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)

A saída é da seguinte maneira:

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 utilizam pelo menos um argumento para que o item seja correspondido, mas eles também podem usar argumentos adicionais. Nesse caso, o padrão ativo parametrizado do nome é aplicável. Os argumentos adicionais permitem que um padrão geral seja especializado. Por exemplo, os padrões ativos que usam expressões regulares para analisar cadeias de caracteres geralmente incluem a expressão regular como parâmetro adicional, como no código a seguir, que também usa o padrão ativo parcial Integer definido no exemplo de código anterior. Neste exemplo, as cadeias de caracteres que usam expressões regulares para vários formatos de data são fornecidas para personalizar o padrão ativo geral ParseRegex. O padrão ativo inteiro é 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())

Veja a seguir a saída do código anterior:

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 são restritos apenas a expressões de padrões correspondentes, você também pode usá-los em associações let.

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

Veja a seguir a saída do código anterior:

Hello, random citizen!
Hello, George!

No entanto, observe que somente 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 struct para padrões ativos parciais

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

open System

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

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

Confira também