Buscar..


Patrones activos simples

Los patrones activos son un tipo especial de coincidencia de patrones donde puede especificar categorías con nombre en las que sus datos pueden caer, y luego usar esas categorías en las declaraciones de match .

Para definir un patrón activo que clasifique los números como positivo, negativo o cero:

let (|Positive|Negative|Zero|) num = 
    if num > 0 then Positive 
    elif num < 0 then Negative
    else Zero

Esto se puede usar en una expresión de coincidencia de patrón:

let Sign value = 
    match value with
    | Positive -> printf "%d is positive" value
    | Negative -> printf "%d is negative" value
    | Zero -> printf "The value is zero"

Sign -19 // -19 is negative
Sign 2 // 2 is positive
Sign 0 // The value is zero

Patrones activos con parámetros.

Los patrones activos son simplemente funciones simples.

Al igual que las funciones puedes definir parámetros adicionales:

let (|HasExtension|_|) expected (uri : string) = 
    let result = uri.EndsWith (expected, StringComparison.CurrentCultureIgnoreCase)
    match result with
    | true -> Some true
    | _ -> None

Esto se puede utilizar en un patrón que coincida de esta manera:

    let isXMLFile uri =
        match uri with
        | HasExtension ".xml" _ -> true
        | _ -> false

Los patrones activos se pueden usar para validar y transformar argumentos de funciones

Un uso interesante pero bastante desconocido de los Patrones Activos en F# es que pueden usarse para validar y transformar argumentos de funciones.

Considera la forma clásica de hacer validación de argumentos:

// val f : string option -> string option -> string
let f v u =
  let v = defaultArg v "Hello"
  let u = defaultArg u "There"
  v + " " + u

// val g : 'T -> 'T (requires 'T null)
let g v =
  match v with
  | null  -> raise (System.NullReferenceException ())
  | _     -> v.ToString ()

Normalmente, agregamos código en el método para verificar que los argumentos son correctos. Usando patrones activos en F# podemos generalizar esto y declarar la intención en la declaración del argumento.

El siguiente código es equivalente al código anterior:

let inline (|DefaultArg|) dv ov = defaultArg ov dv

let inline (|NotNull|) v =
  match v with
  | null  -> raise (System.NullReferenceException ())
  | _     -> v

// val f : string option -> string option -> string
let f (DefaultArg "Hello" v) (DefaultArg "There" u) = v + " " + u

// val g : 'T -> string (requires 'T null)
let g (NotNull v) = v.ToString ()

Para el usuario de la función f y g no hay diferencia entre las dos versiones diferentes.

printfn "%A" <| f (Some "Test") None  // Prints "Test There"
printfn "%A" <| g "Test"              // Prints "Test"
printfn "%A" <| g null                // Will throw

Una preocupación es si los patrones activos agregan sobrecarga de rendimiento. ILSpy para descompilar f y g para ver si ese es el caso.

public static string f(FSharpOption<string> _arg2, FSharpOption<string> _arg1)
{
  return Operators.DefaultArg<string>(_arg2, "Hello") + " " + Operators.DefaultArg<string>(_arg1, "There");
}

public static string g<a>(a _arg1) where a : class
{
  if (_arg1 != null)
  {
    a a = _arg1;
    return a.ToString();
  }
  throw new NullReferenceException();
}

Gracias a inline Active Patterns no agrega una sobrecarga adicional en comparación con la forma clásica de validar argumentos.

Patrones activos como envolturas de API .NET

Los patrones activos se pueden usar para hacer que las API de .NET se sientan más naturales, especialmente aquellas que usan un parámetro de salida para devolver algo más que el valor de retorno de la función.

Por ejemplo, normalmente llamaría al método System.Int32.TryParse siguiente manera:

let couldParse, parsedInt = System.Int32.TryParse("1")
if couldParse then printfn "Successfully parsed int: %i" parsedInt
else printfn "Could not parse int"

Puedes mejorar esto un poco usando la coincidencia de patrones:

match System.Int32.TryParse("1") with 
| (true, parsedInt) -> printfn "Successfully parsed int: %i" parsedInt
| (false, _) -> printfn "Could not parse int"

Sin embargo, también podemos definir el siguiente patrón activo que envuelve la función System.Int32.TryParse :

let (|Int|_|) str =
    match System.Int32.TryParse(str) with
    | (true, parsedInt) -> Some parsedInt
    | _ -> None

Ahora podemos hacer lo siguiente:

match "1" with
| Int parsedInt -> printfn "Successfully parsed int: %i" parsedInt
| _ -> printfn "Could not parse int"

Otro buen candidato para ser envuelto en un patrón activo son las expresiones regulares de la API:

let (|MatchRegex|_|) pattern input =
    let m = System.Text.RegularExpressions.Regex.Match(input, pattern) 
    if m.Success then Some m.Groups.[1].Value 
    else None  

match "bad" with
| MatchRegex "(good|great)" mood -> 
    printfn "Wow, it's a %s day!" mood
| MatchRegex "(bad|terrible)" mood -> 
    printfn "Unfortunately, it's a %s day." mood
| _ -> 
    printfn "Just a normal day"

Patrones activos parciales y completos

Hay dos tipos de patrones activos que difieren un poco en el uso: Completo y Parcial.

Se pueden usar patrones activos completos cuando se pueden enumerar todos los resultados, como "¿es un número impar o par?"

let (|Odd|Even|) number = 
  if number % 2 = 0
  then Even
  else Odd

Observe que la definición de patrón activo enumera los casos posibles y nada más, y el cuerpo devuelve uno de los casos enumerados. Cuando lo usas en una expresión de coincidencia, esta es una coincidencia completa:

let n = 13
match n with
| Odd -> printf "%i is odd" n
| Even -> printf "%i is even" n

Esto es útil cuando desea desglosar el espacio de entrada en categorías conocidas que lo cubren por completo.

Los patrones activos parciales, por otro lado, le permiten ignorar explícitamente algunos resultados posibles al devolver una option . Su definición utiliza un caso especial de _ para el caso no coincidente.

let (|Integer|_|) str =
  match Int32.TryParse(str) with
  | (true, i) -> Some i
  | _ -> None

De esta manera podemos hacer coincidir incluso cuando algunos casos no pueden ser manejados por nuestra función de análisis.

let s = "13"
match s with
| Integer i -> "%i was successfully parsed!" i
| _ -> "%s is not an int" s

Los patrones activos parciales se pueden usar como una forma de prueba, ya sea que la entrada caiga en una categoría específica en el espacio de entrada al ignorar otras opciones.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow