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.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow