Patrones activos

Los patrones activos permiten definir particiones con nombre que subdividen los datos de entrada, de modo que pueda usar estos nombres en una expresión de coincidencia de patrones como lo haría para 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 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 activa. La expresión describe el formulario en el que se descomponen los datos. Puede usar una definición de patrón activa para definir las reglas para determinar a qué particiones con nombre pertenecen los valores dados como argumentos. Los símbolos (| y |) se conocen como clips de manzana y la función creada por este tipo de enlace let se denomina 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 los patrones activos es descomponer los tipos de datos de varias maneras, como cuando los mismos datos subyacentes tienen varias representaciones posibles. Por ejemplo, un Color objeto podría descomponerse en una representación RGB o 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 formas de usar patrones activos le permiten particionar y descomponer los datos en la forma adecuada y realizar los cálculos adecuados en los datos adecuados en el formulario más conveniente para el cálculo.

Las expresiones de coincidencia de patrones resultantes permiten escribir datos de una manera cómoda que sea muy legible, lo que simplifica en gran medida el código de análisis de datos y bifurcación potencialmente complejo.

Patrones activos parciales

A veces, solo es necesario particionar 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 coincide con otras entradas. Los patrones activos que no siempre generan 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, se usa un carácter comodín ( ) al final de la lista de patrones dentro de los clips _ de manzana. El código siguiente 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 ser disociadas o mutuamente excluyentes, pero no deben serlo. En el ejemplo siguiente, el patrón Square y el patrón Cube no están desconexos, ya que algunos números son cuadrados y cubos, como 64. El programa siguiente usa el patrón AND para combinar los patrones Square y Cube. Imprime todos los enteros de hasta 1000 que son cuadrados y cubos, así como 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 con parámetros

Los patrones activos siempre toman al menos un argumento para el elemento que coincide, pero también pueden tomar argumentos adicionales, en cuyo caso se aplica el patrón activo con parámetros de nombre. Los argumentos adicionales permiten especializar un patrón general. 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 definido en el ejemplo de código Integer anterior. En este ejemplo, se dan cadenas que usan expresiones regulares para varios formatos de fecha para personalizar el patrón activo ParseRegex general. 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 solo 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 los 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 para patrones activos parciales

De forma predeterminada, los patrones activos parciales devuelven un valor, lo que implicará una asignación option para el valor en una coincidencia Some 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

Se debe especificar el atributo , ya que el uso de un valor devuelto 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.

Vea también