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.