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.