Zoeken…
Eenvoudige actieve patronen
Actieve patronen zijn een speciaal type patroonmatching waarbij u categorieën met namen kunt opgeven waarin uw gegevens kunnen vallen en deze categorieën vervolgens kunt gebruiken in match
.
Om een actief patroon te definiëren dat getallen classificeert als positief, negatief of nul:
let (|Positive|Negative|Zero|) num =
if num > 0 then Positive
elif num < 0 then Negative
else Zero
Dit kan vervolgens worden gebruikt in een patroonuitdrukking:
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
Actieve patronen met parameters
Actieve patronen zijn slechts eenvoudige functies.
Net als functies kunt u aanvullende parameters definiëren:
let (|HasExtension|_|) expected (uri : string) =
let result = uri.EndsWith (expected, StringComparison.CurrentCultureIgnoreCase)
match result with
| true -> Some true
| _ -> None
Dit kan worden gebruikt in een patroon dat op deze manier overeenkomt:
let isXMLFile uri =
match uri with
| HasExtension ".xml" _ -> true
| _ -> false
Actieve patronen kunnen worden gebruikt om functieargumenten te valideren en te transformeren
Een interessant maar tamelijk onbekend gebruik van actieve patronen in F#
is dat ze kunnen worden gebruikt om functieargumenten te valideren en te transformeren.
Overweeg de klassieke manier om argumentvalidatie uit te voeren:
// 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 ()
Doorgaans voegen we code toe aan de methode om te controleren of argumenten correct zijn. Met actieve patronen in F#
we dit generaliseren en de intentie in de argumentdeclaratie verklaren.
De volgende code is gelijk aan de bovenstaande code:
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 ()
Voor de gebruiker van functie f
en g
er geen verschil tussen de twee verschillende versies.
printfn "%A" <| f (Some "Test") None // Prints "Test There"
printfn "%A" <| g "Test" // Prints "Test"
printfn "%A" <| g null // Will throw
Een zorg is als actieve patronen prestaties overhead toevoegen. Laten we ILSpy
om f
en g
te decompileren om te zien of dat het geval is.
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();
}
Dankzij inline
voegt de actieve patronen geen extra overhead toe in vergelijking met de klassieke manier van validatie van het argument.
Actieve patronen als .NET API-wrappers
Actieve patronen kunnen worden gebruikt om het aanroepen van sommige .NET API's natuurlijker aan te laten voelen, met name die welke een uitvoerparameter gebruiken om meer te retourneren dan alleen de functie retourwaarde.
Normaal zou u bijvoorbeeld de methode System.Int32.TryParse
als volgt aanroepen:
let couldParse, parsedInt = System.Int32.TryParse("1")
if couldParse then printfn "Successfully parsed int: %i" parsedInt
else printfn "Could not parse int"
Je kunt dit een beetje verbeteren met behulp van patroonovereenkomst:
match System.Int32.TryParse("1") with
| (true, parsedInt) -> printfn "Successfully parsed int: %i" parsedInt
| (false, _) -> printfn "Could not parse int"
We kunnen echter ook het volgende actieve patroon definiëren dat de System.Int32.TryParse
functie System.Int32.TryParse
:
let (|Int|_|) str =
match System.Int32.TryParse(str) with
| (true, parsedInt) -> Some parsedInt
| _ -> None
Nu kunnen we het volgende doen:
match "1" with
| Int parsedInt -> printfn "Successfully parsed int: %i" parsedInt
| _ -> printfn "Could not parse int"
Een andere goede kandidaat om te worden verpakt in een actief patroon zijn de API's voor reguliere expressies:
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"
Volledige en gedeeltelijk actieve patronen
Er zijn twee soorten actieve patronen die enigszins verschillen in gebruik - compleet en gedeeltelijk.
Volledige actieve patronen kunnen worden gebruikt wanneer u alle resultaten kunt opsommen, zoals "is een aantal even of oneven?"
let (|Odd|Even|) number =
if number % 2 = 0
then Even
else Odd
Merk op dat de definitie van Actief patroon beide mogelijke gevallen en niets anders vermeldt, en de body een van de vermelde gevallen retourneert. Wanneer u het in een overeenkomstuitdrukking gebruikt, is dit een volledige overeenkomst:
let n = 13
match n with
| Odd -> printf "%i is odd" n
| Even -> printf "%i is even" n
Dit is handig als u de invoerruimte wilt opsplitsen in bekende categorieën die deze volledig afdekken.
Met Gedeeltelijk actieve patronen kunt u bepaalde mogelijke resultaten expliciet negeren door een option
retourneren. Hun definitie gebruikt een speciaal geval van _
voor het ongeëvenaarde geval.
let (|Integer|_|) str =
match Int32.TryParse(str) with
| (true, i) -> Some i
| _ -> None
Op deze manier kunnen we matchen, zelfs als sommige gevallen niet door onze parsing-functie kunnen worden afgehandeld.
let s = "13"
match s with
| Integer i -> "%i was successfully parsed!" i
| _ -> "%s is not an int" s
Gedeeltelijk actieve patronen kunnen worden gebruikt als een vorm van testen, of de invoer in een specifieke categorie in de invoerruimte valt, terwijl andere opties worden genegeerd.