Sök…
Enkla aktiva mönster
Aktiva mönster är en speciell typ av mönstermatchning där du kan ange namngivna kategorier som dina data kan falla in i och sedan använda de kategorierna i match
.
För att definiera ett aktivt mönster som klassificerar siffror som positiva, negativa eller noll:
let (|Positive|Negative|Zero|) num =
if num > 0 then Positive
elif num < 0 then Negative
else Zero
Detta kan sedan användas i ett mönster-matchande uttryck:
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
Aktiva mönster med parametrar
Aktiva mönster är bara enkla funktioner.
Som funktioner kan du definiera ytterligare parametrar:
let (|HasExtension|_|) expected (uri : string) =
let result = uri.EndsWith (expected, StringComparison.CurrentCultureIgnoreCase)
match result with
| true -> Some true
| _ -> None
Detta kan användas i ett mönster som matchar detta sätt:
let isXMLFile uri =
match uri with
| HasExtension ".xml" _ -> true
| _ -> false
Aktiva mönster kan användas för att validera och omvandla funktionsargument
En intressant men ganska okänd användning av aktiva mönster i F#
är att de kan användas för att validera och omvandla funktionsargument.
Tänk på det klassiska sättet att göra argumentvalidering:
// 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 ()
Vanligtvis lägger vi till kod i metoden för att verifiera att argument är korrekta. Med hjälp av aktiva mönster i F#
vi generalisera detta och förklara avsikten i argumentdeklarationen.
Följande kod motsvarar koden ovan:
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 ()
För användaren av funktionen f
och g
finns det ingen skillnad mellan de två olika versionerna.
printfn "%A" <| f (Some "Test") None // Prints "Test There"
printfn "%A" <| g "Test" // Prints "Test"
printfn "%A" <| g null // Will throw
Ett problem är om Active Patterns lägger till högre prestanda. Låt oss använda ILSpy
att dekompilera f
och g
att se om så är fallet.
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();
}
Tack vare inline
lägger de aktiva mönstren inga extra omkostnader jämfört med det klassiska sättet att göra argumentvalidering.
Aktiva mönster som .NET API-omslag
Aktiva mönster kan användas för att göra att vissa .NET API: s känns mer naturliga, särskilt de som använder en utgångsparameter för att returnera mer än bara funktionens returvärde.
Till exempel kan du normalt ringa metoden System.Int32.TryParse
enligt följande:
let couldParse, parsedInt = System.Int32.TryParse("1")
if couldParse then printfn "Successfully parsed int: %i" parsedInt
else printfn "Could not parse int"
Du kan förbättra detta lite genom att använda mönstermatchning:
match System.Int32.TryParse("1") with
| (true, parsedInt) -> printfn "Successfully parsed int: %i" parsedInt
| (false, _) -> printfn "Could not parse int"
Vi kan emellertid också definiera följande aktiva mönster som System.Int32.TryParse
funktionen System.Int32.TryParse
:
let (|Int|_|) str =
match System.Int32.TryParse(str) with
| (true, parsedInt) -> Some parsedInt
| _ -> None
Nu kan vi göra följande:
match "1" with
| Int parsedInt -> printfn "Successfully parsed int: %i" parsedInt
| _ -> printfn "Could not parse int"
En annan bra kandidat för att vara insvept i ett aktivt mönster är de vanliga uttrycks-API: er:
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"
Kompletta och delvis aktiva mönster
Det finns två typer av aktiva mönster som något skiljer sig i användning - komplett och delvis.
Kompletta aktiva mönster kan användas när du kan räkna upp alla resultat, som "är ett antal udda eller jämnt?"
let (|Odd|Even|) number =
if number % 2 = 0
then Even
else Odd
Lägg märke till att definitionen av aktivt mönster listar både möjliga fall och inget annat, och kroppen returnerar ett av de listade fallen. När du använder det i ett matchuttryck är detta en komplett matchning:
let n = 13
match n with
| Odd -> printf "%i is odd" n
| Even -> printf "%i is even" n
Detta är praktiskt när du vill dela in ingångsutrymmet i kända kategorier som täcker det helt.
Partiella aktiva mönster å andra sidan låter dig uttryckligen ignorera några möjliga resultat genom att returnera ett option
. Deras definition använder ett specialfall av _
för det oöverträffade fallet.
let (|Integer|_|) str =
match Int32.TryParse(str) with
| (true, i) -> Some i
| _ -> None
På det här sättet kan vi matcha även om vissa fall inte kan hanteras med vår parsing-funktion.
let s = "13"
match s with
| Integer i -> "%i was successfully parsed!" i
| _ -> "%s is not an int" s
Partiella aktiva mönster kan användas som en form av testning, om ingången faller in i en viss kategori i ingångsutrymmet medan man ignorerar andra alternativ.