Modelos activos (F#)
Los modelos activos permiten definir particiones con nombre que subdividen los datos de entrada de modo que se pueden usar estos nombres en una expresión de coincidencia de modelos tal y como se utilizarían en el caso de una unión discriminada. Se pueden usar modelos activos para descomponer los datos de manera personalizada para cada partición.
// Complete active pattern definition.
let (|identifer1|identifier2|...|) [ arguments ] = expression
// Partial active pattern definition.
let (|identifier1|identifier2|...|_|) [ arguments ] = expression
Comentarios
En la sintaxis anterior, los identificadores son nombres para las particiones de los datos de entrada representados por arguments; en otras palabras, son nombres para los subconjuntos del conjunto de todos los valores de los argumentos. Puede haber hasta siete particiones en una definición de modelo activo. expression describe la forma en que se van a descomponer los datos. Se puede utilizar una definición de modelo activo para definir las reglas por las que se determinan las particiones con nombre a las que 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 denomina reconocedor activo.
A título de ejemplo, observe el siguiente modelo activo con un argumento.
let (|Even|Odd|) input = if input % 2 = 0 then Even else Odd
El modelo activo se puede usar en una expresión de coincidencia de modelos, 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
Los modelos activos también se pueden usar para descomponer los tipos de datos de varias maneras, como en el caso en que los mismos datos subyacentes tienen varias representaciones posibles. Por ejemplo, un objeto Color puede descomponerse en una representación RGB 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"
La salida del programa anterior es la siguiente:
Red
R: 255 G: 0 B: 0
H: 0.000000 S: 1.000000 B: 0.500000
Black
R: 0 G: 0 B: 0
H: 0.000000 S: 0.000000 B: 0.000000
White
R: 255 G: 255 B: 255
H: 0.000000 S: 0.000000 B: 1.000000
Gray
R: 128 G: 128 B: 128
H: 0.000000 S: 0.000000 B: 0.501961
BlanchedAlmond
R: 255 G: 235 B: 205
H: 36.000000 S: 1.000000 B: 0.901961
Juntas, estas dos formas de usar modelos activos permiten crear particiones y descomponer los datos de la manera más apropiada, además de procesar de la forma más conveniente los datos apropiados.
Las expresiones de coincidencia de modelos resultantes permiten escribir los datos de manera cómoda y muy legible, simplificando en gran medida el código de bifurcación y de análisis de datos, que de otra manera sería complejo.
Modelos activos parciales
A veces, es necesario crear particiones únicamente de una parte del espacio de entrada. En ese caso, debe programar un conjunto de modelos parciales, cada uno de los cuales solo detecta una parte de la entrada. Los modelos activos que no siempre generan un valor se denominan modelos activos parciales; el valor devuelto es de tipo opción. Para definir un modelo activo parcial, se utiliza un carácter comodín (_) al final de la lista de modelos especificados entre los delimitadores de modelo activo. En el código siguiente se muestra el uso de un modelo 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 modelos activos parciales, las opciones individuales pueden no estar relacionadas o ser mutuamente excluyentes, aunque no necesariamente. En el siguiente ejemplo, el modelo Square y el modelo Cube están relacionados porque algunos números son cuadrados y cubos, como en el caso del número 64. El siguiente programa imprime todos los enteros hasta 1000000 que son cuadrados y cubos.
let err = 1.e-10
let floatequal x y =
abs (x - y) < err
let (|Square|_|) (x : int) =
if floatequal (sqrt (float x)) (round (sqrt (float x))) then Some(x)
else None
let (|Cube|_|) (x : int) =
if floatequal ((float x) ** ( 1.0 / 3.0)) (round ((float x) ** (1.0 / 3.0))) then Some(x)
else None
let examineNumber x =
match x with
| Cube x -> printfn "%d is a cube" x
| _ -> ()
match x with
| Square x -> printfn "%d is a square" x
| _ -> ()
let findSquareCubes x =
if (match x with
| Cube x -> true
| _ -> false
&&
match x with
| Square x -> true
| _ -> false
)
then printf "%d \n" x
[ 1 .. 1000000 ] |> List.iter (fun elem -> findSquareCubes elem)
La salida es la siguiente:
1
64
729
4096
15625
46656
117649
262144
531441
1000000
Modelos activos parametrizados
Los modelos activos siempre reciben como mínimo un argumento para el elemento del que se van a buscar coincidencias. Sin embargo, pueden recibir argumentos adicionales, en cuyo caso se denominarán modelos activos parametrizados. Los argumentos adicionales permiten la especialización de un modelo general. Por ejemplo, los modelos activos que utilizan expresiones regulares para analizar cadenas suelen incluir la expresión regular como un parámetro adicional, como en el código siguiente, donde también se utiliza el modelo activo parcial Integer definido en el ejemplo de código anterior. En este ejemplo se usan cadenas que proporcionan expresiones regulares para varios formatos de fecha a fin de personalizar el modelo activo ParseRegex general. Se usa el modelo activo Integer 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