Ricerca…


Modelli attivi semplici

I pattern attivi sono un tipo speciale di pattern matching in cui è possibile specificare le categorie denominate in cui i dati potrebbero cadere e quindi utilizzare tali categorie nelle dichiarazioni match .

Per definire un modello attivo che classifica i numeri come positivi, negativi o pari a zero:

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

Questo può quindi essere utilizzato in un'espressione di corrispondenza del modello:

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

Pattern attivi con parametri

Gli schemi attivi sono solo semplici funzioni.

Come le funzioni puoi definire parametri aggiuntivi:

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

Questo può essere utilizzato in un modello che corrisponde in questo modo:

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

I pattern attivi possono essere utilizzati per convalidare e trasformare gli argomenti delle funzioni

Un uso interessante ma piuttosto sconosciuto di Active Pattern in F# è che possono essere usati per validare e trasformare gli argomenti delle funzioni.

Considera il modo classico di convalidare argomenti:

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

In genere aggiungiamo codice nel metodo per verificare che gli argomenti siano corretti. Usando Pattern attivi in F# possiamo generalizzare questo e dichiarare l'intenzione nella dichiarazione di argomento.

Il seguente codice è equivalente al codice sopra:

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 ()

Per l'utente della funzione f e g non c'è differenza tra le due versioni differenti.

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

Un problema è se i Pattern attivi aggiungono un sovraccarico alle prestazioni. Usiamo ILSpy per decompilare f e g per vedere se è così.

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();
}

Grazie a inline Active Patterns non aggiunge extra overhead rispetto al classico modo di convalidare l'argomento doing.

Pattern attivi come wrapper API .NET

I pattern attivi possono essere utilizzati per rendere più naturale la chiamata ad alcune API .NET, in particolare quelle che utilizzano un parametro di output per restituire più del semplice valore restituito dalla funzione.

Ad esempio, normalmente chiameresti il ​​metodo System.Int32.TryParse come segue:

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

Puoi migliorarlo un po 'usando la corrispondenza del modello:

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

Tuttavia, possiamo anche definire il seguente pattern attivo che avvolge la funzione System.Int32.TryParse :

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

Ora possiamo fare quanto segue:

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

Un altro buon candidato per essere inclusi in un Pattern attivo sono le API delle espressioni regolari:

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"

Modelli attivi completi e parziali

Esistono due tipi di Pattern attivi che differiscono in qualche modo nell'utilizzo: Completo e Parziale.

I modelli attivi completi possono essere utilizzati quando è possibile enumerare tutti i risultati, ad esempio "è un numero pari o dispari?"

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

Si noti che la definizione del modello attivo elenca entrambi i casi possibili e nient'altro, e il corpo restituisce uno dei casi elencati. Quando lo usi in un'espressione di corrispondenza, questa è una corrispondenza completa:

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

Questo è utile quando si desidera suddividere lo spazio di input in categorie note che lo coprano completamente.

D'altra parte, i pattern attivi parziali consentono di ignorare esplicitamente alcuni risultati restituendo option . La loro definizione utilizza un caso speciale di _ per il caso ineguagliato.

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

In questo modo possiamo eguagliare anche quando alcuni casi non possono essere gestiti dalla nostra funzione di analisi.

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

I Pattern attivi parziali possono essere utilizzati come forma di test, indipendentemente dal fatto che l'input cada in una categoria specifica nello spazio di input ignorando le altre opzioni.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow