Patrones activos

Los patrones activos permiten definir particiones con nombre que subdividen datos de entrada, de modo que pueda usar estos nombres en una expresión de coincidencia de patrones igual que lo haría en una unión discriminada. Se pueden usar patrones activos para descomponer los datos de manera 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.
// Uses a FSharp.Core.option<_> to represent if the type is satisfied at the call site.
let (|identifier|_|) [arguments] valueToMatch = expression

Comentarios

En la sintaxis anterior, los identificadores son nombres de particiones de los datos de entrada representados por argumentos, es decir, nombres de 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 la forma en que se descomponen los datos. Puede usar una definición de patrón activo para definir las reglas para determinar a qué particiones con nombre pertenecen los valores dados 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

El resultado de este programa es el siguiente:

7 is odd
11 is odd
32 is even

Otro uso de los patrones activos es descomponer tipos de datos de varias maneras, por ejemplo, cuando los mismos datos subyacentes tienen varias representaciones posibles. Por ejemplo, un objeto Color se podría descomponer 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"

El resultado del programa anterior es el 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 formas de usar patrones activos permiten crear particiones y descomponer los datos en el formato adecuado y realizar los cálculos apropiados en los datos correctos en el formato más cómodo para el cálculo.

Las expresiones de coincidencia de patrones resultantes permiten escribir los datos de una manera cómoda 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 se necesita crear particiones únicamente de parte del espacio de entrada. En ese caso se escribe un conjunto de patrones parciales, cada uno de los cuales coincide con algunas entradas pero no con otras. Los patrones activos que no siempre producen un valor se conocen como 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.

Cuando se usan patrones activos parciales, a veces las opciones individuales pueden estar separadas o ser mutuamente excluyentes, aunque no tienen por qué. En el ejemplo siguiente, el patrón Square y el patrón Cube no están separados, ya que algunos números son tanto cuadrados como cubos, por ejemplo, 64. El siguiente programa usa el patrón AND para combinar los patrones Square y Cube. Imprime todos los enteros hasta 1000 que son cuadrados y cubos, además de los 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 como sigue:

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 parametrizados

Los patrones activos siempre toman al menos un argumento del elemento que se va a comparar, pero también pueden tomar argumentos adicionales, en cuyo caso se aplica el nombre patrón activo parametrizado. 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 un 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 de varios formatos de fecha a fin de 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 un solo caso.

// 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

Representaciones de estructura de patrones activos parciales

De manera predeterminada, los patrones activos parciales devuelven un valor option, lo que implica una asignación del valor Some en una coincidencia correcta. Como alternativa, 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, ya que el uso de una devolución de estructura no se deduce simplemente del cambio del tipo de valor devuelto a ValueOption. Para obtener más información, vea RFC FS-1039.

Vea también