Szukaj…
Proste aktywne wzory
Aktywne wzorce to specjalny rodzaj dopasowywania wzorców, w którym można określić nazwane kategorie, do których mogą należeć dane, a następnie użyć tych kategorii w instrukcjach match
.
Aby zdefiniować aktywny wzorzec, który klasyfikuje liczby jako dodatnie, ujemne lub zerowe:
let (|Positive|Negative|Zero|) num =
if num > 0 then Positive
elif num < 0 then Negative
else Zero
Można to następnie zastosować w wyrażeniu dopasowującym wzór:
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
Aktywne wzorce z parametrami
Aktywne wzorce to tylko proste funkcje.
Podobnie jak funkcje możesz zdefiniować dodatkowe parametry:
let (|HasExtension|_|) expected (uri : string) =
let result = uri.EndsWith (expected, StringComparison.CurrentCultureIgnoreCase)
match result with
| true -> Some true
| _ -> None
Można tego użyć we wzorcu pasującym w ten sposób:
let isXMLFile uri =
match uri with
| HasExtension ".xml" _ -> true
| _ -> false
Aktywnych wzorców można używać do sprawdzania poprawności i transformacji argumentów funkcji
Ciekawym, ale raczej nieznanym zastosowaniem Active Patterns w F#
jest to, że można ich używać do sprawdzania poprawności i transformacji argumentów funkcji.
Rozważ klasyczny sposób sprawdzania poprawności argumentów:
// 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 ()
Zazwyczaj dodajemy kod do metody, aby sprawdzić, czy argumenty są poprawne. Używając Active Patterns w F#
możemy to uogólnić i zadeklarować zamiar w deklaracji argumentu.
Poniższy kod jest równoważny powyższemu kodowi:
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 ()
Dla użytkownika funkcji f
i g
nie ma różnicy między dwiema różnymi wersjami.
printfn "%A" <| f (Some "Test") None // Prints "Test There"
printfn "%A" <| g "Test" // Prints "Test"
printfn "%A" <| g null // Will throw
Niepokojące jest to, czy aktywne wzorce zwiększają wydajność. Użyjmy ILSpy
do dekompilacji f
i g
aby sprawdzić, czy tak jest.
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();
}
Dzięki inline
Active Patterns nie dodaje dodatkowego obciążenia w porównaniu z klasycznym sposobem sprawdzania poprawności argumentów.
Aktywne wzorce jako opakowania API .NET
Aktywne wzorce mogą być używane, aby wywoływanie niektórych interfejsów API platformy .NET wydawało się bardziej naturalne, szczególnie tych, które używają parametru wyjściowego do zwracania więcej niż tylko zwracanej wartości funkcji.
Na przykład normalnie wywołujesz metodę System.Int32.TryParse
w następujący sposób:
let couldParse, parsedInt = System.Int32.TryParse("1")
if couldParse then printfn "Successfully parsed int: %i" parsedInt
else printfn "Could not parse int"
Możesz to nieco poprawić, używając dopasowania wzorca:
match System.Int32.TryParse("1") with
| (true, parsedInt) -> printfn "Successfully parsed int: %i" parsedInt
| (false, _) -> printfn "Could not parse int"
Możemy jednak również zdefiniować następujący aktywny wzorzec, który otacza funkcję System.Int32.TryParse
:
let (|Int|_|) str =
match System.Int32.TryParse(str) with
| (true, parsedInt) -> Some parsedInt
| _ -> None
Teraz możemy wykonać następujące czynności:
match "1" with
| Int parsedInt -> printfn "Successfully parsed int: %i" parsedInt
| _ -> printfn "Could not parse int"
Innym dobrym kandydatem do zawinięcia w aktywne wzorce są interfejsy API wyrażeń regularnych:
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"
Kompletne i częściowo aktywne wzorce
Istnieją dwa rodzaje aktywnych wzorców, które nieco różnią się w użyciu - kompletne i częściowe.
Kompletnych aktywnych wzorców można użyć, gdy jesteś w stanie wyliczyć wszystkie wyniki, np. „Czy liczba jest nieparzysta czy parzysta?”
let (|Odd|Even|) number =
if number % 2 = 0
then Even
else Odd
Zauważ, że definicja aktywnego wzorca zawiera zarówno możliwe przypadki, jak i nic więcej, a treść zwraca jeden z wymienionych przypadków. Gdy użyjesz go w wyrażeniu dopasowania, jest to pełne dopasowanie:
let n = 13
match n with
| Odd -> printf "%i is odd" n
| Even -> printf "%i is even" n
Jest to przydatne, gdy chcesz podzielić przestrzeń wejściową na znane kategorie, które całkowicie ją obejmują.
Z drugiej strony częściowe aktywne wzorce pozwalają jawnie zignorować niektóre możliwe wyniki, zwracając option
. W ich definicji użyto specjalnego przypadku _
dla niedopasowanego przypadku.
let (|Integer|_|) str =
match Int32.TryParse(str) with
| (true, i) -> Some i
| _ -> None
W ten sposób możemy dopasować, nawet jeśli niektóre przypadki nie mogą być obsługiwane przez naszą funkcję parsowania.
let s = "13"
match s with
| Integer i -> "%i was successfully parsed!" i
| _ -> "%s is not an int" s
Częściowe aktywne wzorce można wykorzystać jako formę testowania, czy dane wejściowe należą do określonej kategorii w przestrzeni wejściowej, ignorując inne opcje.