Teilen über


Berechnungsausdrücke

Berechnungsausdrücke in F# bieten eine bequeme Syntax zum Schreiben von Berechnungen, die durch Kontrollflusskonstrukte und Bindungen sequenziert und kombiniert werden können. Je nach Art des Berechnungsausdrucks können sie als eine Möglichkeit zur Darstellung von Monaden, Monoiden, Monadtransformatoren und applikativen Funktoren betrachtet werden. Im Gegensatz zu anderen Sprachen (z. B. do-notation in Haskell) sind sie jedoch nicht an eine einzelne Abstraktion gebunden und basieren nicht auf Makros oder andere Formen der Metaprogrammierung, um eine bequeme und kontextabhängige Syntax zu erreichen.

Überblick

Berechnungen können viele Formen annehmen. Die häufigste Form der Berechnung ist die einfädige Ausführung, die leicht zu verstehen und zu ändern ist. Allerdings sind nicht alle Berechnungsformen so einfach wie die Einzelthreadausführung. Einige Beispiele sind:

  • Nicht deterministische Berechnungen
  • Asynchrone Berechnungen
  • Effektvolle Berechnungen
  • Generative Berechnungen

Im Allgemeinen gibt es kontextabhängige Berechnungen (), die Sie in bestimmten Teilen einer Anwendung () ausführen müssen. Das Schreiben von kontextsensitivem Code kann schwierig sein, da es leicht passieren kann, dass Berechnungen ohne Abstraktionen, die verhindern, dass dies geschieht, außerhalb eines gegebenen Kontexts durchsickern. Es ist oft schwierig, diese Abstraktionen selbst zu schreiben; daher bietet F# eine generalisierte Möglichkeit dafür: die so genannten Berechnungsausdrücke.

Berechnungsausdrücke bieten ein einheitliches Syntax- und Abstraktionsmodell für die Codierung kontextabhängiger Berechnungen.

Jeder Berechnungsausdruck wird durch einen Buildertyp unterstützt. Der Buildertyp definiert die Vorgänge, die für den Berechnungsausdruck verfügbar sind. Informationen zum Erstellen eines benutzerdefinierten Berechnungsausdrucks finden Sie unter Erstellen einer neuen Art von Berechnungsausdruck.

Syntaxübersicht

Alle Berechnungsausdrücke haben die folgende Form:

builder-expr { cexper }

In dieser Form ist builder-expr der Name eines Buildertyps, der den Berechnungsausdruck definiert, und cexper ist der Ausdruckstext des Berechnungsausdrucks. Beispielsweise kann async-Berechnungsausdruckscode wie folgt aussehen:

let fetchAndDownload url =
    async {
        let! data = downloadData url

        let processedData = processData data

        return processedData
    }

Es gibt eine spezielle, zusätzliche Syntax, die in einem Berechnungsausdruck verfügbar ist, wie im vorherigen Beispiel gezeigt. Die folgenden Ausdrucksformulare sind mit Berechnungsausdrücken möglich:

expr { let! ... }
expr { and! ... }
expr { do! ... }
expr { yield ... }
expr { yield! ... }
expr { return ... }
expr { return! ... }
expr { match! ... }

Jedes dieser Schlüsselwörter sowie andere standardmäßige F#-Schlüsselwörter sind nur in einem Berechnungsausdruck verfügbar, wenn sie im unterstützenden Buildertyp definiert wurden. Die einzige Ausnahme ist das Schlüsselwort match!, das selbst syntaktischer Zucker für die Verwendung von let! gefolgt von einem Musterabgleich im Ergebnis ist.

Der Generatortyp ist ein Objekt, das spezielle Methoden definiert, die steuern, wie die Fragmente des Berechnungsausdrucks kombiniert werden; d. h. seine Methoden steuern, wie sich der Berechnungsausdruck verhält. Builderklassen lassen sich auch dadurch beschreiben, dass Sie die Verarbeitung vieler F#-Konstrukte wie z. B. Schleifen und Bindungen anpassen können.

let!

Das let!-Schlüsselwort weist das Ergebnis eines Aufrufs eines anderen Berechnungsausdrucks einem Namen zu.

let doThingsAsync url =
    async {
        let! data = getDataAsync url
        ...
    }

Wenn Sie den Aufruf an einen Berechnungsausdruck mit letbinden, erhalten Sie das Ergebnis des Berechnungsausdrucks nicht. Stattdessen haben Sie den Wert des nicht ausgeführten Aufrufs dieses Berechnungsausdrucks gebunden. Verwenden Sie let!, um eine Bindung an das Ergebnis zu erstellen.

let! wird durch den Bind(x, f)-Member im Buildertyp definiert.

and!

Mit dem Schlüsselwort and! können Sie die Ergebnisse mehrerer Berechnungsausdrucksaufrufe effizient verknüpfen.

let doThingsAsync url =
    async {
        let! data = getDataAsync url
        and! moreData = getMoreDataAsync anotherUrl
        and! evenMoreData = getEvenMoreDataAsync someUrl
        ...
    }

Die Verwendung einer Reihe von let! ... let! ... erzwingt die erneute Ausführung teurer Bindungen, sodass zum Binden zahlreicher Berechnungsausdrücke let! ... and! ... verwendet werden sollte.

and! wird in erster Linie durch den MergeSources(x1, x2)-Member im Buildertyp definiert.

Optional kann MergeSourcesN(x1, x2 ..., xN) definiert werden, um die Anzahl der Tupelknoten zu verringern, und BindN(x1, x2 ..., xN, f) oder BindNReturn(x1, x2, ..., xN, f) können definiert werden, um Ergebnisse von Berechnungsausdrücken effizient ohne Tupelknoten zu binden.

do!

Das Schlüsselwort do! dient zum Aufrufen eines Berechnungsausdrucks, der einen Typ ähnlich unit zurückgibt (definiert durch den Zero-Member im Builder):

let doThingsAsync data url =
    async {
        do! submitData data url
        ...
    }

Für den asynchronen Workflow lautet dieser Typ Async<unit>. Bei anderen Berechnungsausdrücken ist der Typ wahrscheinlich CExpType<unit>.

do! wird durch den Bind(x, f)-Member im Buildertyp definiert, wobei f ein unit-Element erzeugt.

yield

Das Schlüsselwort yield dient zum Zurückgeben eines Werts aus dem Berechnungsausdruck, sodass dieser als IEnumerable<T> verwendet werden kann:

let squares =
    seq {
        for i in 1..10 do
            yield i * i
    }

for sq in squares do
    printfn $"%d{sq}"

In den meisten Fällen kann sie von Anrufern weggelassen werden. Die am häufigsten verwendete Methode zum Weglassen von yield ist der ->-Operator:

let squares =
    seq {
        for i in 1..10 -> i * i
    }

for sq in squares do
    printfn $"%d{sq}"

Bei komplexeren Ausdrücken, die möglicherweise viele verschiedene Werte liefern und möglicherweise Bedingungen unterliegen, kann ein einfaches Weglassen des Schlüsselworts ausreichen:

let weekdays includeWeekend =
    seq {
        "Monday"
        "Tuesday"
        "Wednesday"
        "Thursday"
        "Friday"
        if includeWeekend then
            "Saturday"
            "Sunday"
    }

Wie bei dem Schlüsselwort „yield“ in C# wird jedes Element im Berechnungsausdruck beim Durchlaufen sofort zurückgegeben.

yield wird durch den Yield(x)-Member im Buildertyp definiert, wobei x das Element ist, das zurückgegeben werden soll.

yield!

Das Schlüsselwort yield! dient zum Vereinfachen einer Auflistung von Werten aus einem Berechnungsausdruck:

let squares =
    seq {
        for i in 1..3 -> i * i
    }

let cubes =
    seq {
        for i in 1..3 -> i * i * i
    }

let squaresAndCubes =
    seq {
        yield! squares
        yield! cubes
    }

printfn $"{squaresAndCubes}"  // Prints - 1; 4; 9; 1; 8; 27

Bei der Auswertung erhält der von yield! aufgerufene Berechnungsausdruck seine Elemente einzeln zurück, wodurch das Ergebnis vereinfacht wird.

yield! wird durch den YieldFrom(x)-Member im Buildertyp definiert, wobei x eine Auflistung von Werten ist.

Im Gegensatz zu yieldmuss yield! explizit angegeben werden. Das Verhalten ist in Berechnungsausdrücken nicht implizit.

return

Das Schlüsselwort return umschließt einen Wert in dem Typ, der dem Berechnungsausdruck entspricht. Abgesehen von Berechnungsausdrücken mit yield wird es verwendet, um einen Berechnungsausdruck „abzuschließen“:

let req = // 'req' is of type 'Async<data>'
    async {
        let! data = fetch url
        return data
    }

// 'result' is of type 'data'
let result = Async.RunSynchronously req

return wird durch den Return(x)-Member im Buildertyp definiert, wobei x das Element ist, das umschlossen werden soll. Bei let! ... return kann BindReturn(x, f) zur Verbesserung der Leistung verwendet werden.

return!

Das Schlüsselwort return! erkennt den Wert eines Berechnungsausdrucks und umschließt dieses Ergebnis in dem Typ, der dem Berechnungsausdruck entspricht:

let req = // 'req' is of type 'Async<data>'
    async {
        return! fetch url
    }

// 'result' is of type 'data'
let result = Async.RunSynchronously req

return! wird durch den ReturnFrom(x)-Member im Buildertyp definiert, wobei x ein weiterer Berechnungsausdruck ist.

match!

Mit dem Schlüsselwort match! können Sie einen Aufruf eines anderen Berechnungsausdrucks und den Musterabgleich für dessen Ergebnis inline einfügen:

let doThingsAsync url =
    async {
        match! callService url with
        | Some data -> ...
        | None -> ...
    }

Beim Aufrufen eines Berechnungsausdrucks mit match! wird das Ergebnis eines Aufrufs wie let! erkannt. Dies wird häufig beim Aufrufen eines Berechnungsausdrucks verwendet, bei dem das Ergebnis optional ist.

Integrierte Berechnungsausdrücke

Die F#-Kernbibliothek definiert vier integrierte Berechnungsausdrücke: Sequenzausdrücke, Asynchrone Ausdrücke, Aufgabenausdrückeund Abfrageausdrücke.

Erstellen einer neuen Art von Berechnungsausdruck

Sie können die Merkmale Ihrer eigenen Berechnungsausdrücke definieren, indem Sie eine Generatorklasse erstellen und bestimmte spezielle Methoden für die Klasse definieren. Die Generatorklasse kann optional die In der folgenden Tabelle aufgeführten Methoden definieren.

In der folgenden Tabelle werden Methoden beschrieben, die in einer Workflow-Generator-Klasse verwendet werden können.

Methode Typische Signatur(en) Beschreibung:\
Bind M<'T> * ('T -> M<'U>) -> M<'U> Wird für let! und do! in Berechnungsausdrücken aufgerufen.
BindN (M<'T1> * M<'T2> * ... * M<'TN> * ('T1 * 'T2 ... * 'TN -> M<'U>)) -> M<'U> Wird für die effiziente Verwendung von let! und and! in Berechnungsausdrücken ohne Zusammenführen von Eingaben aufgerufen.

Beispiele: Bind3, Bind4.
Delay (unit -> M<'T>) -> Delayed<'T> Umschließt einen Berechnungsausdruck als Funktion. Delayed<'T> kann ein beliebiger Typ sein, häufig werden M<'T> oder unit -> M<'T> verwendet. Die Standardimplementierung gibt einen M<'T>-Typ zurück.
Return 'T -> M<'T> Wird für return in Berechnungsausdrücken aufgerufen.
ReturnFrom M<'T> -> M<'T> Wird für return! in Berechnungsausdrücken aufgerufen.
BindReturn (M<'T1> * ('T1 -> 'T2)) -> M<'T2> Wird für die effiziente Verwendung von let! ... return in Berechnungsausdrücken aufgerufen.
BindNReturn (M<'T1> * M<'T2> * ... * M<'TN> * ('T1 * 'T2 ... * 'TN -> M<'U>)) -> M<'U> Wird für die effiziente Verwendung von let! ... and! ... return in Berechnungsausdrücken ohne Zusammenführen von Eingaben aufgerufen.

Beispiele: Bind3Return, Bind4Return.
MergeSources (M<'T1> * M<'T2>) -> M<'T1 * 'T2> Wird für and! in Berechnungsausdrücken aufgerufen.
MergeSourcesN (M<'T1> * M<'T2> * ... * M<'TN>) -> M<'T1 * 'T2 * ... * 'TN> Wird für and! in Berechnungsausdrücken aufgerufen, verbessert jedoch die Effizienz durch Reduzieren der Anzahl von Tupelknoten.

Beispiele: MergeSources3, MergeSources4.
Run Delayed<'T> -> M<'T> oder

M<'T> -> 'T
Führt einen Berechnungsausdruck aus.
Combine M<'T> * Delayed<'T> -> M<'T> oder

M<unit> * M<'T> -> M<'T>
Wird für die Sequenzierung in Berechnungsausdrücken aufgerufen.
For seq<'T> * ('T -> M<'U>) -> M<'U> oder

seq<'T> * ('T -> M<'U>) -> seq<M<'U>>
Wird für for...do-Ausdrücke in Berechnungsausdrücken aufgerufen.
TryFinally Delayed<'T> * (unit -> unit) -> M<'T> Wird für try...finally-Ausdrücke in Berechnungsausdrücken aufgerufen.
TryWith Delayed<'T> * (exn -> M<'T>) -> M<'T> Wird für try...with-Ausdrücke in Berechnungsausdrücken aufgerufen.
Using 'T * ('T -> M<'U>) -> M<'U> when 'T :> IDisposable Wird für use-Bindungen in Berechnungsausdrücken aufgerufen.
While (unit -> bool) * Delayed<'T> -> M<'T>oder

(unit -> bool) * Delayed<unit> -> M<unit>
Wird für while...do-Ausdrücke in Berechnungsausdrücken aufgerufen.
Yield 'T -> M<'T> Wird für yield-Ausdrücke in Berechnungsausdrücken aufgerufen.
YieldFrom M<'T> -> M<'T> Wird für yield!-Ausdrücke in Berechnungsausdrücken aufgerufen.
Zero unit -> M<'T> Wird für leere else-Verzweigungen von if...then-Ausdrücken in Berechnungsausdrücken aufgerufen.
Quote Quotations.Expr<'T> -> Quotations.Expr<'T> Gibt an, dass der Berechnungsausdruck als Zitat an den Run-Member übergeben wird. Übersetzt alle Instanzen einer Berechnung in ein Zitat.

Viele der Methoden in einer Generatorklasse verwenden und geben ein M<'T>-Konstrukt zurück, das in der Regel ein separat definierter Typ ist, der die Art der kombinierten Berechnungen kennzeichnet, z. B. Async<'T> für asynchrone Ausdrücke und Seq<'T> für Sequenzworkflows. Signaturen dieser Methoden ermöglichen es, sie miteinander zu kombinieren und zu verschachteln, sodass das von einem Konstrukt zurückgegebene Workflow-Objekt an das nächste Konstrukt übergeben werden kann.

Viele Funktionen verwenden das Ergebnis von Delay als Argument: Run, While, TryWith, TryFinallyund Combine. Der Typ Delayed<'T> ist der Rückgabetyp von Delay und folglich der Parameter für diese Funktionen. Delayed<'T> kann ein beliebiger Typ sein, der nicht mit M<'T>verknüpft sein muss; häufig werden M<'T> oder (unit -> M<'T>) verwendet. Die Standardimplementierung ist M<'T>. Detailliertere Informationen finden Sie hier.

Beim Parsen eines Berechnungsausdrucks übersetzt der Compiler den Ausdruck mithilfe der Methoden in der vorherigen Tabelle und des Codes im Berechnungsausdruck in eine Reihe geschachtelter Funktionsaufrufe. Der geschachtelte Ausdruck weist die folgende Form auf:

builder.Run(builder.Delay(fun () -> {{ cexpr }}))

Im obigen Code werden die Aufrufe von Run und Delay weggelassen, wenn sie in der Builderklasse für den Berechnungsausdruck nicht definiert sind. Der Textkörper des Berechnungsausdrucks, hier als {{ cexpr }} angegeben, wird in weitere Aufrufe der Methoden der Generatorklasse übersetzt. Dieser Prozess wird rekursiv gemäß den Übersetzungen in der folgenden Tabelle definiert. Code in doppelten Klammern {{ ... }} ist noch zu übersetzen, expr stellt einen F#-Ausdruck dar und cexpr einen Berechnungsausdruck.

expression Übersetzung
{{ let binding in cexpr }} let binding in {{ cexpr }}
{{ let! pattern = expr in cexpr }} builder.Bind(expr, (fun pattern -> {{ cexpr }}))
{{ do! expr in cexpr }} builder.Bind(expr, (fun () -> {{ cexpr }}))
{{ yield expr }} builder.Yield(expr)
{{ yield! expr }} builder.YieldFrom(expr)
{{ return expr }} builder.Return(expr)
{{ return! expr }} builder.ReturnFrom(expr)
{{ use pattern = expr in cexpr }} builder.Using(expr, (fun pattern -> {{ cexpr }}))
{{ use! value = expr in cexpr }} builder.Bind(expr, (fun value -> builder.Using(value, (fun value -> {{ cexpr }}))))
{{ if expr then cexpr0 }} if expr then {{ cexpr0 }} else builder.Zero()
{{ if expr then cexpr0 else cexpr1 }} if expr then {{ cexpr0 }} else {{ cexpr1 }}
{{ match expr with | pattern_i -> cexpr_i }} match expr with | pattern_i -> {{ cexpr_i }}
{{ for pattern in enumerable-expr do cexpr }} builder.For(enumerable-expr, (fun pattern -> {{ cexpr }}))
{{ for identifier = expr1 to expr2 do cexpr }} builder.For([expr1..expr2], (fun identifier -> {{ cexpr }}))
{{ while expr do cexpr }} builder.While(fun () -> expr, builder.Delay({{ cexpr }}))
{{ try cexpr with | pattern_i -> expr_i }} builder.TryWith(builder.Delay({{ cexpr }}), (fun value -> match value with | pattern_i -> expr_i | exn -> System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(exn).Throw()))
{{ try cexpr finally expr }} builder.TryFinally(builder.Delay({{ cexpr }}), (fun () -> expr))
{{ cexpr1; cexpr2 }} builder.Combine({{ cexpr1 }}, {{ cexpr2 }})
{{ other-expr; cexpr }} expr; {{ cexpr }}
{{ other-expr }} expr; builder.Zero()

In der vorherigen Tabelle beschreibt other-expr einen Ausdruck, der nicht anderweitig in der Tabelle aufgeführt ist. Eine Generatorklasse muss nicht alle Methoden implementieren und alle in der vorherigen Tabelle aufgeführten Übersetzungen unterstützen. Diese nicht implementierten Konstrukte sind in Berechnungsausdrücken dieses Typs nicht verfügbar. Wenn Sie beispielsweise das Schlüsselwort use in Ihren Berechnungsausdrücken nicht unterstützen möchten, können Sie die Definition von Use in der Builder-Klasse weglassen.

Das folgende Codebeispiel zeigt einen Berechnungsausdruck, der eine Berechnung als Reihe von Schritten einkapselt, die Schritt für Schritt ausgewertet werden können. Ein diskriminierter Union-Typ, OkOrException, codiert den Fehlerzustand des Ausdrucks, so weit er bisher ausgewertet ist. Dieser Code veranschaulicht mehrere typische Muster, die Sie in Ihren Berechnungsausdrücken verwenden können, wie zum Beispiel Standardimplementierungen von Generatormethoden.

/// Represents computations that can be run step by step
type Eventually<'T> =
    | Done of 'T
    | NotYetDone of (unit -> Eventually<'T>)

module Eventually =

    /// Bind a computation using 'func'.
    let rec bind func expr =
        match expr with
        | Done value -> func value
        | NotYetDone work -> NotYetDone (fun () -> bind func (work()))

    /// Return the final value
    let result value = Done value

    /// The catch for the computations. Stitch try/with throughout
    /// the computation, and return the overall result as an OkOrException.
    let rec catch expr =
        match expr with
        | Done value -> result (Ok value)
        | NotYetDone work ->
            NotYetDone (fun () ->
                let res = try Ok(work()) with | exn -> Error exn
                match res with
                | Ok cont -> catch cont // note, a tailcall
                | Error exn -> result (Error exn))

    /// The delay operator.
    let delay func = NotYetDone (fun () -> func())

    /// The stepping action for the computations.
    let step expr =
        match expr with
        | Done _ -> expr
        | NotYetDone func -> func ()

    /// The tryFinally operator.
    /// This is boilerplate in terms of "result", "catch", and "bind".
    let tryFinally expr compensation =
        catch (expr)
        |> bind (fun res ->
            compensation();
            match res with
            | Ok value -> result value
            | Error exn -> raise exn)

    /// The tryWith operator.
    /// This is boilerplate in terms of "result", "catch", and "bind".
    let tryWith exn handler =
        catch exn
        |> bind (function Ok value -> result value | Error exn -> handler exn)

    /// The whileLoop operator.
    /// This is boilerplate in terms of "result" and "bind".
    let rec whileLoop pred body =
        if pred() then body |> bind (fun _ -> whileLoop pred body)
        else result ()

    /// The sequential composition operator.
    /// This is boilerplate in terms of "result" and "bind".
    let combine expr1 expr2 =
        expr1 |> bind (fun () -> expr2)

    /// The using operator.
    /// This is boilerplate in terms of "tryFinally" and "Dispose".
    let using (resource: #System.IDisposable) func =
        tryFinally (func resource) (fun () -> resource.Dispose())

    /// The forLoop operator.
    /// This is boilerplate in terms of "catch", "result", and "bind".
    let forLoop (collection:seq<_>) func =
        let ie = collection.GetEnumerator()
        tryFinally
            (whileLoop
                (fun () -> ie.MoveNext())
                (delay (fun () -> let value = ie.Current in func value)))
            (fun () -> ie.Dispose())

/// The builder class.
type EventuallyBuilder() =
    member x.Bind(comp, func) = Eventually.bind func comp
    member x.Return(value) = Eventually.result value
    member x.ReturnFrom(value) = value
    member x.Combine(expr1, expr2) = Eventually.combine expr1 expr2
    member x.Delay(func) = Eventually.delay func
    member x.Zero() = Eventually.result ()
    member x.TryWith(expr, handler) = Eventually.tryWith expr handler
    member x.TryFinally(expr, compensation) = Eventually.tryFinally expr compensation
    member x.For(coll:seq<_>, func) = Eventually.forLoop coll func
    member x.Using(resource, expr) = Eventually.using resource expr

let eventually = new EventuallyBuilder()

let comp =
    eventually {
        for x in 1..2 do
            printfn $" x = %d{x}"
        return 3 + 4
    }

/// Try the remaining lines in F# interactive to see how this
/// computation expression works in practice.
let step x = Eventually.step x

// returns "NotYetDone <closure>"
comp |> step

// prints "x = 1"
// returns "NotYetDone <closure>"
comp |> step |> step

// prints "x = 1"
// prints "x = 2"
// returns "Done 7"
comp |> step |> step |> step |> step

Ein Berechnungsausdruck weist einen zugrunde liegenden Typ auf, den der Ausdruck zurückgibt. Der zugrunde liegende Typ kann ein berechnetes Ergebnis oder eine verzögerbare Berechnung darstellen, die ausgeführt werden kann, oder eine Möglichkeit bieten, durch eine bestimmte Art von Sammlung zu iterieren. Im vorherigen Beispiel war der zugrunde liegende Typ Eventually<_>. Für einen Sequenzausdruck ist der zugrunde liegende Typ System.Collections.Generic.IEnumerable<T>. Für einen Abfrageausdruck ist der zugrunde liegende Typ System.Linq.IQueryable. Für einen asynchronen Ausdruck ist der zugrunde liegende Typ Async. Das Async-Objekt stellt die auszuführende Arbeit dar, um das Ergebnis zu berechnen. Sie rufen beispielsweise Async.RunSynchronously auf, um eine Berechnung auszuführen und das Ergebnis zurückzugeben.

Benutzerdefinierte Vorgänge

Sie können einen benutzerdefinierten Vorgang für einen Berechnungsausdruck definieren und einen benutzerdefinierten Vorgang als Operator in einem Berechnungsausdruck verwenden. Sie können z. B. einen Abfrageoperator in einen Abfrageausdruck einschließen. Wenn Sie einen benutzerdefinierten Vorgang definieren, müssen Sie die Methoden „Yield“ und „For“ im Berechnungsausdruck definieren. Um einen benutzerdefinierten Vorgang zu definieren, fügen Sie ihn in eine Builderklasse für den Berechnungsausdruck ein, und wenden Sie dann das CustomOperationAttribute an. Dieses Attribut verwendet eine Zeichenfolge als Argument, bei dem es sich um den Namen handelt, der in einem benutzerdefinierten Vorgang verwendet werden soll. Dieser Name wird am Anfang der öffnenden geschweiften Klammer des Berechnungsausdrucks in den Bereich eingefügt. Daher sollten Sie keine Bezeichner verwenden, die denselben Namen wie ein benutzerdefinierter Vorgang in diesem Block haben. Vermeiden Sie beispielsweise die Verwendung von Bezeichnern wie all oder last in Abfrageausdrücken.

Erweitern vorhandener Builder mit neuen benutzerdefinierten Vorgängen

Wenn Sie bereits über eine Builderklasse verfügen, können die benutzerdefinierten Vorgänge von außerhalb dieser Builderklasse erweitert werden. Erweiterungen müssen in Modulen deklariert werden. Namespaces dürfen keine Erweiterungsmitglieder enthalten, außer in derselben Datei und in derselben Namespace-Deklarationsgruppe, in der der Typ definiert ist.

Das folgende Beispiel zeigt die Erweiterungen der vorhandenen FSharp.Linq.QueryBuilder Klasse.

open System
open FSharp.Linq

type QueryBuilder with

    [<CustomOperation>]
    member _.any (source: QuerySource<'T, 'Q>, predicate) =
        System.Linq.Enumerable.Any (source.Source, Func<_,_>(predicate))

    [<CustomOperation("singleSafe")>] // you can specify your own operation name in the constructor 
    member _.singleOrDefault (source: QuerySource<'T, 'Q>, predicate) =
        System.Linq.Enumerable.SingleOrDefault (source.Source, Func<_,_>(predicate))

Benutzerdefinierte Vorgänge können überladen werden. Weitere Informationen finden Sie unter F# RFC FS-1056 – Allow overloads of custom keywords in computation expressions.

Effizientes Kompilieren von Berechnungsausdrücken

F#-Berechnungsausdrücke, die die Ausführung anhalten, können durch umsichtige Verwendung eines Features auf niedriger Ebene, das als wiederaufnehmbarer Code bezeichnet wird, in hocheffiziente Zustandsautomaten kompiliert werden. Diese Art von Code ist in F# RFC FS-1087 dokumentiert und wird für Aufgabenausdrücke verwendet.

F#-Berechnungsausdrücke, die synchron sind (d. h. die Ausführung nicht anhalten), können alternativ mithilfe von Inlinefunktionen einschließlich des InlineIfLambda-Attributs in effiziente Zustandsautomaten kompiliert werden. Beispiele werden in F# RFC FS-1098angegeben.

Listenausdrücke, Arrayausdrücke und Sequenzausdrücke werden vom F#-Compiler speziell behandelt, um die Generierung von Hochleistungscode sicherzustellen.

Weitere Informationen