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.