Patrones activos
Los patrones activos nos permiten definir particiones con nombre que subdividen los datos de entrada, de modo que puedan usar estos nombres en una expresión de coincidencia de patrones exactamente como lo haría para una unión discriminada. Puede usar patrones activos para descomponer los datos de forma personalizada para cada partición.
Sintaxis
// 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.
// Can use FSharp.Core.option<_>, FSharp.Core.voption<_> or bool to represent if the type is satisfied at the call site.
let (|identifier|_|) [arguments] valueToMatch = expression
Observaciones
En la sintaxis anterior, los identificadores son nombres para las particiones de los datos de entrada representados por argumentos, o, en otras palabras, nombres para subconjuntos del conjunto de todos los valores de los argumentos. Puede haber hasta siete particiones en una definición de patrón activo. La expresión describe el formulario en el que se van a descomponer los datos. Puede usar una definición de patrón activo para definir las reglas para determinar a cuál de las particiones con nombre pertenecen los valores proporcionados como argumentos. Los símbolos (| y |) se conocen como delimitadores de modelo activo y la función creada por este tipo de enlace let se conoce como reconocedor activo.
Por ejemplo, considere el siguiente patrón activo con un argumento .
let (|Even|Odd|) input = if input % 2 = 0 then Even else Odd
Puede usar el patrón activo en una expresión de coincidencia de patrones, como en el ejemplo siguiente.
let TestNumber input =
match input with
| Even -> printfn "%d is even" input
| Odd -> printfn "%d is odd" input
TestNumber 7
TestNumber 11
TestNumber 32
La salida de este programa es la siguiente:
7 is odd
11 is odd
32 is even
Otro uso de patrones activos es descomponer tipos de datos de varias maneras, como cuando los mismos datos subyacentes tienen varias representaciones posibles. Por ejemplo, un objeto Color
podría descomponerse en una representación RGB o en una representación 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"
La salida del programa anterior es la siguiente:
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
En combinación, estas dos maneras de usar patrones activos permiten particionar y descomponer los datos en el formato adecuado y realizar los cálculos adecuados en los datos adecuados en el formato más conveniente para el cálculo.
Las expresiones de coincidencia de patrones resultantes permiten escribir datos de una manera cómoda que es muy legible, lo que simplifica considerablemente la bifurcación potencialmente compleja y el código de análisis de datos.
Patrones activos parciales
A veces, solo debe particionar parte del espacio de entrada. En ese caso, se escribe un conjunto de patrones parciales que coinciden con algunas entradas, pero no coinciden con otras entradas. Los patrones activos que no siempre producen un valor se denominan patrones activos parciales; tienen un valor devuelto que es un tipo de opción. Para definir un patrón activo parcial, use un carácter comodín (_) al final de la lista de patrones del interior de los delimitadores de modelo activo. En el código siguiente se muestra el uso de un patrón activo 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"
La salida del ejemplo anterior es la siguiente:
1.100000 : Floating point
0 : Integer
0.000000 : Floating point
10 : Integer
Something else : Not matched.
Al usar patrones activos parciales, a veces las opciones individuales pueden ser discordantes o mutuamente excluyentes, pero no tienen que serlo. En el ejemplo siguiente, el patrón Cuadrado y el patrón Cubo no son disjuntos, ya que algunos números son tanto cuadrados como cubos, como 64. El siguiente programa usa el patrón AND para combinar los patrones Square y Cube. Imprime todos los enteros hasta 1000 que son tanto cuadrados como cubos, así como aquellos que son solo 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)
La salida es la siguiente:
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
Patrones activos con parámetros
Los patrones activos siempre toman al menos un argumento para el elemento que se busca, pero también pueden tomar argumentos adicionales, en cuyo caso se aplica el nombre patrón activo con parámetros. Los argumentos adicionales permiten que un patrón general sea especializado. Por ejemplo, los patrones activos que usan expresiones regulares para analizar cadenas suelen incluir la expresión regular como parámetro adicional, como en el código siguiente, que también usa el patrón activo parcial Integer
definido en el ejemplo de código anterior. En este ejemplo, se proporcionan cadenas que usan expresiones regulares para varios formatos de fecha para personalizar el patrón activo general ParseRegex. El patrón activo Integer se usa para convertir las cadenas coincidentes en enteros que se pueden pasar al constructor 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())
La salida del código anterior es la siguiente:
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
Los patrones activos no están restringidos a las expresiones de coincidencia de patrones, sino que también se pueden usar en enlaces 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")
La salida del código anterior es la siguiente:
Hello, random citizen!
Hello, George!
Sin embargo, tenga en cuenta que solo se pueden parametrizar patrones activos de caso único.
// 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
Tipo de valor devuelto para patrones activos parciales
Los patrones activos parciales devuelven Some ()
para indicar una coincidencia y None
de lo contrario.
Considere este partido:
match key with
| CaseInsensitive "foo" -> ...
| CaseInsensitive "bar" -> ...
El patrón activo parcial para él sería:
let (|CaseInsensitive|_|) (pattern: string) (value: string) =
if String.Equals(value, pattern, StringComparison.OrdinalIgnoreCase) then
Some ()
else
None
A partir de F# 9, estos patrones también pueden devolver bool
:
let (|CaseInsensitive|_|) (pattern: string) (value: string) =
String.Equals(value, pattern, StringComparison.OrdinalIgnoreCase)
Representaciones de estructura para patrones activos parciales
Por defecto, si un patrón activo parcial devuelve option
, esto implicará una asignación para el valor de Some
en caso de coincidencia correcta. Para evitarlo, puede usar una opción de valor como valor devuelto mediante el uso del atributo Struct
:
open System
[<return: Struct>]
let (|Int|_|) str =
match Int32.TryParse(str) with
| (true, n) -> ValueSome n
| _ -> ValueNone
El atributo debe especificarse, porque el uso de un retorno de struct no se deduce simplemente al cambiar el tipo de valor devuelto a ValueOption
. Para obtener más información, vea RFC FS-1039.