Suche…


Einfache aktive Muster

Aktive Muster sind eine spezielle Art der Mustererkennung, in der Sie benannte Kategorien angeben können, in die Ihre Daten fallen können, und diese Kategorien dann in match .

So definieren Sie ein aktives Muster, das Zahlen als positiv, negativ oder null klassifiziert:

let (|Positive|Negative|Zero|) num = 
    if num > 0 then Positive 
    elif num < 0 then Negative
    else Zero

Dies kann dann in einem Mustervergleichsausdruck verwendet werden:

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

Aktive Muster mit Parametern

Aktive Muster sind nur einfache Funktionen.

Wie Funktionen können Sie zusätzliche Parameter definieren:

let (|HasExtension|_|) expected (uri : string) = 
    let result = uri.EndsWith (expected, StringComparison.CurrentCultureIgnoreCase)
    match result with
    | true -> Some true
    | _ -> None

Dies kann in einem Muster verwendet werden, das auf diese Weise passt:

    let isXMLFile uri =
        match uri with
        | HasExtension ".xml" _ -> true
        | _ -> false

Aktive Muster können zur Überprüfung und Umwandlung von Funktionsargumenten verwendet werden

Eine interessante, aber eher unbekannte Verwendung von Active Patterns in F# besteht darin, dass sie zum Validieren und Transformieren von Funktionsargumenten verwendet werden können.

Betrachten Sie die klassische Art der Argumentvalidierung:

// 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 ()

Normalerweise fügen wir der Methode Code hinzu, um zu überprüfen, ob die Argumente korrekt sind. Mit Active Patterns in F# wir dies verallgemeinern und die Absicht in der Argumentdeklaration deklarieren.

Der folgende Code entspricht dem obigen 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 ()

Für den Benutzer der Funktion f und g gibt es keinen Unterschied zwischen den beiden verschiedenen Versionen.

printfn "%A" <| f (Some "Test") None  // Prints "Test There"
printfn "%A" <| g "Test"              // Prints "Test"
printfn "%A" <| g null                // Will throw

Ein Problem ist, wenn Active Patterns Performance-Overhead hinzufügen. Verwenden wir ILSpy um f und g zu dekompilieren, um zu sehen, ob dies der Fall ist.

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();
}

Dank inline fügen die Active Patterns keinen zusätzlichen Overhead hinzu, verglichen mit der klassischen Methode der Argumentvalidierung.

Aktive Muster als .NET-API-Wrapper

Active Patterns können verwendet werden, um das Aufrufen einiger .NET-APIs natürlicher zu machen, insbesondere solche, die einen Ausgabeparameter verwenden, um mehr als nur den Funktionsrückgabewert zurückzugeben.

Beispielsweise würden Sie normalerweise die Methode System.Int32.TryParse wie folgt System.Int32.TryParse :

let couldParse, parsedInt = System.Int32.TryParse("1")
if couldParse then printfn "Successfully parsed int: %i" parsedInt
else printfn "Could not parse int"

Sie können dies mit dem Pattern-Matching verbessern:

match System.Int32.TryParse("1") with 
| (true, parsedInt) -> printfn "Successfully parsed int: %i" parsedInt
| (false, _) -> printfn "Could not parse int"

Wir können jedoch auch das folgende aktive Muster definieren, das die Funktion System.Int32.TryParse :

let (|Int|_|) str =
    match System.Int32.TryParse(str) with
    | (true, parsedInt) -> Some parsedInt
    | _ -> None

Jetzt können wir folgendes tun:

match "1" with
| Int parsedInt -> printfn "Successfully parsed int: %i" parsedInt
| _ -> printfn "Could not parse int"

Ein weiterer guter Kandidat für die Umhüllung in aktive Muster sind die APIs für reguläre Ausdrücke:

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"

Vollständige und teilweise aktive Muster

Es gibt zwei Arten von aktiven Mustern, die sich in der Verwendung etwas unterscheiden - vollständig und teilweise.

Vollständige aktive Muster können verwendet werden, wenn Sie alle Ergebnisse auflisten können, z. B. "Ist eine Zahl ungerade oder gerade?"

let (|Odd|Even|) number = 
  if number % 2 = 0
  then Even
  else Odd

Beachten Sie, dass die Definition des aktiven Musters sowohl mögliche Fälle als auch nichts anderes aufführt, und der Hauptteil gibt einen der aufgeführten Fälle zurück. Wenn Sie es in einem Übereinstimmungsausdruck verwenden, ist dies eine vollständige Übereinstimmung:

let n = 13
match n with
| Odd -> printf "%i is odd" n
| Even -> printf "%i is even" n

Dies ist praktisch, wenn Sie den Eingabebereich in bekannte Kategorien unterteilen möchten, die ihn vollständig abdecken.

Partial Active Patterns hingegen lassen Sie mögliche Ergebnisse explizit ignorieren, indem Sie eine option . Ihre Definition verwendet einen Sonderfall von _ für den nicht übereinstimmenden Fall.

let (|Integer|_|) str =
  match Int32.TryParse(str) with
  | (true, i) -> Some i
  | _ -> None

Auf diese Weise können wir auch dann übereinstimmen, wenn einige Fälle von unserer Parsing-Funktion nicht behandelt werden können.

let s = "13"
match s with
| Integer i -> "%i was successfully parsed!" i
| _ -> "%s is not an int" s

Teilweise aktive Muster können als eine Form des Testens verwendet werden, ob die Eingabe in eine bestimmte Kategorie im Eingabebereich fällt, während andere Optionen ignoriert werden.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow