Berechnungsausdrücke (F#)
Berechnungsausdrücke in F# stellen eine zweckmäßige Syntax zum Schreiben von Berechnungen bereit, die sequenziert und anhand von Ablaufsteuerungskonstrukten und Bindungen verbunden werden können. Sie können verwendet werden, um eine zweckmäßige Syntax für Monaden bereitzustellen, eine funktionale Programmierfunktion, die verwendet werden kann, um Daten-, Steuerelement- und Nebeneffekte in funktionalen Programmen zu verwalten.
Integrierte Workflows
Sequenz-Ausdrücke sind ein Beispiel für eine Berechnung Ausdruck wie asynchrone Workflows und Abfrageausdrücke. Weitere Informationen finden Sie unter folgen, Asynchronen Workflows, und Abfrageausdrücke.
Sequenzausdrücke und asynchrone Workflows weisen bestimmte gemeinsame Funktionen auf, die die grundlegende Syntax für einen Berechnungsausdruck veranschaulichen:
builder-name { expression }
Die vorherige Syntax gibt an, dass der angegebene Ausdruck ein Berechnungsausdruck eines von builder-name angegebenen Typs ist. Der Berechnungsausdruck kann ein integrierter Workflow sein, z. B. seq oder async oder benutzerdefiniert. Der builder-name ist der Bezeichner für eine Instanz eines speziellen Typs, der als Generatortyp bezeichnet wird. Der Generatortyp ist ein Klassentyp, der die speziellen Methoden für die Art und Weise, wie Fragmente des Berechnungsausdrucks kombiniert werden, definiert. Es handelt sich also um Code, der die Ausführung des Ausdrucks steuert. Eine Generatorklasse ermöglicht Ihnen, die Operation vieler F#-Konstrukte anzupassen, z. B. Schleifen und Bindungen.
In Berechnungsausdrücken sind zwei Formen für einige häufig verwendete Sprachkonstrukte verfügbar. Sie können die verschiedenen Konstrukte mit ! aufrufen. (Bang) hinter bestimmten Schlüsselwörtern aufgerufen, z. B. let!, do! usw. Aufgrund dieser speziellen Formen wird das gewöhnliche integrierte Verhalten dieser Vorgänge durch bestimmte in der Generatorklasse definierte Funktionen ersetzt. Diese Formen ähneln der yield!-Form des in Sequenzausdrücken verwendeten yield-Schlüsselworts. Weitere Informationen finden Sie unter Sequenzen.
Erstellen eines neuen Berechnungsausdruckstyps
Sie können die Merkmale eigener Berechnungsausdrücke definieren, indem Sie eine Generatorklasse erstellen und bestimmte spezielle Methoden für die Klasse definieren. Von der Generatorklasse können die Methoden optional definiert werden, wie in der folgenden Tabelle aufgeführt.
In der folgenden Tabelle werden Methoden beschrieben, die in einer Workflow-Generatorklasse verwendet werden können.
Methode |
Typische Signatur |
Beschreibung |
Bind |
M<'T> * ('T -> M<'U>) -> M<'U> |
Für let! und do! in Berechnungsausdrücken aufgerufen. |
Delay |
(unit -> M<'T>) -> M<'T> |
Fasst einen Berechnungsausdruck zu einer Funktion zusammen. |
Return |
'T -> M<'T> |
Für return in Berechnungsausdrücken aufgerufen. |
ReturnFrom |
M<'T> -> M<'T> |
Für return! in Berechnungsausdrücken aufgerufen. |
Run |
M<'T> -> M<'T> oder M<'T> -> 'T |
Führt einen Berechnungsausdruck aus. |
Combine |
M<'T> * M<'T> -> M<'T> oder M<unit> * M<'T> -> M<'T> |
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>> |
Für for...do-Ausdrücke in Berechnungsausdrücken aufgerufen. |
TryFinally |
M<'T> * (unit -> unit) -> M<'T> |
Für try...finally-Ausdrücke in Berechnungsausdrücken aufgerufen. |
TryWith |
M<'T> * (exn -> M<'T>) -> M<'T> |
Für try...with-Ausdrücke in Berechnungsausdrücken aufgerufen. |
Using |
'T * ('T -> M<'U>) -> M<'U> when 'U :> IDisposable |
Für use-Bindungen in Berechnungsausdrücken aufgerufen. |
While |
(unit -> bool) * M<'T> -> M<'T> |
Für while...do-Ausdrücke in Berechnungsausdrücken aufgerufen. |
Yield |
'T -> M<'T> |
Für yield-Ausdrücke in Berechnungsausdrücken aufgerufen. |
YieldFrom |
M<'T> -> M<'T> |
Für yield!-Ausdrücke in Berechnungsausdrücken aufgerufen. |
Zero |
unit -> M<'T> |
Für leere else-Verzweigungen von if...then-Ausdrücken in Berechnungsausdrücken aufgerufen. |
Viele der Methoden in einer Klasse Builder und ein M<'T> Konstrukt, in der Regel einer separat definierten Typ, der die Art von Berechnungen, die kombiniert werden, z. B. kennzeichnet, Async<'T> für asynchrone Workflows und Seq<'T> für Sequenz-Workflows. Die Signaturen dieser Methoden ermöglichen ihre Kombination und Schachtelung, damit das von einem Konstrukt zurückgegebene Workflowobjekt an das Nächste übergeben werden kann. Wenn der Compiler einen Berechnungsausdruck analysiert, konvertiert er den Ausdruck mit den Methoden in der vorhergehenden Tabelle und dem Code im Berechnungsausdruck in eine Reihe geschachtelter Funktionsaufrufe.
Der geschachtelte Ausdruck hat folgende Form:
builder.Run(builder.Delay(fun () -> {| cexpr |}))
Im obigen Code werden die Aufrufe von Run und Delay weggelassen, wenn sie in der Generatorklasse des Berechnungsausdrucks nicht definiert sind. Der Text des Berechnungsausdrucks, hier als {| cexpr |} bezeichnet, wird anhand der in der folgenden Tabelle beschriebenen Übersetzungen in Aufrufe übersetzt, die die Methoden der Generatorklasse verwenden. Der Berechnungsausdruck {| cexpr |} ist entsprechend diesen Übersetzungen rekursiv definiert, wobei expr einen F#-Ausdruck und cexpr einen Berechnungsausdruck darstellt.
Ausdruck |
Ü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(expr1, (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 binder.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 expr do cexpr |} |
builder.For(enumeration, (fun pattern -> {| cexpr }|)) |
{| for identifier = expr1 to expr2 do cexpr |} |
builder.For(enumeration, (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 -> reraise exn))) |
{| 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 sonst in der Tabelle nicht aufgeführt ist. Eine Generatorklasse muss nicht alle Methoden implementieren und nicht alle Übersetzungen unterstützen, die in der vorherigen Tabelle aufgeführt sind. Konstrukte, die nicht implementiert wurden, sind in Berechnungsausdrücken dieses Typs nicht verfügbar. Wenn Sie z. B. das use-Schlüsselwort in Berechnungsausdrücken nicht benötigen, können Sie die Definition von Use in der Generatorklasse weglassen.
Das folgende Codebeispiel zeigt einen Berechnung-Ausdruck, der eine Berechnung kapselt eine Reihe von Schritten, die sich einen Schritt nacheinander ausgewertet. A disjunkten union-Typ, OkOrException, den Fehlerstatus des Ausdrucks codiert, wie bisher ausgewertet. Dieser Code veranschaulicht mehrere typische Muster, die Sie in Ihre Berechnung Ausdrücke, wie z. B. Boilerplate Implementierungen einiger Generator-Methoden verwenden können.
// Computations that can be run step by step
type Eventually<'T> =
| Done of 'T
| NotYetDone of (unit -> Eventually<'T>)
module Eventually =
// The bind for the computations. Append 'func' to the
// computation.
let rec bind func expr =
match expr with
| Done value -> NotYetDone (fun () -> func value)
| NotYetDone work -> NotYetDone (fun () -> bind func (work()))
// Return the final value wrapped in the Eventually type.
let result value = Done value
type OkOrException<'T> =
| Ok of 'T
| Exception of System.Exception
// 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 -> Exception exn
match res with
| Ok cont -> catch cont // note, a tailcall
| Exception exn -> result (Exception 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 rest of the operations are boilerplate.
// 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
| Exception 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 | Exception 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.
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 "NotYetDone <closure>"
comp |> step |> step |> step |> step |> step |> step
// prints "x = 1"
// prints "x = 2"
// returns "Done 7"
comp |> step |> step |> step |> step |> step |> step |> step |> step
Ein Ausdruck für die Berechnung hat einen zugrunde liegenden Typ, die der Ausdruck zurückgibt. Der zugrunde liegende Typ kann darstellen, eine berechnete Ergebnis oder einer verzögerten Berechnung, die ausgeführt werden können, oder sie bieten die Möglichkeit, eine Art von Auflistung durchlaufen kann. Im vorherigen Beispiel der zugrunde liegende Typ war Eventually.Für einen Sequenzausdruck der zugrunde liegende Typ ist IEnumerable. Der zugrunde liegende Typ eines Abfrage-Ausdrucks ist IQueryable. Für einen Workflow Asychronous der zugrunde liegende Typ ist Async. Die Async -Objekt darstellt, die Arbeit durchgeführt werden, um das Ergebnis zu berechnen. Angenommen, Sie rufen Async.RunSynchronously eine Berechnung ausführen und das Ergebnis zurückgegeben.
Benutzerdefinierte Vorgänge
Sie können definieren einen benutzerdefinierten Vorgang für einen Ausdruck für die Berechnung und verwenden Sie einen benutzerdefinierten Vorgang als Operator in einem Ausdruck für die Berechnung. Beispielsweise können Sie einen Abfrageoperator in einem Abfrageausdruck enthalten. Wenn Sie einen benutzerdefinierten Vorgang definieren, müssen Sie die Rendite und für Methoden in der Berechnung-Ausdruck. Um einen benutzerdefinierten Vorgang definieren, legen Sie es in eine Generatorklasse für den Ausdruck der Berechnung, und wenden Sie dann die CustomOperationAttribute. Dieses Attribut akzeptiert eine Zeichenfolge als Argument, das der Name in einem benutzerdefinierten Vorgang verwendet werden. Dieser Name kommt in den Bereich der zu Beginn der öffnenden geschweiften Klammer des Ausdrucks Berechnung. Aus diesem Grund sollten Sie Bezeichner, die den gleichen Namen wie eine benutzerdefinierte Vorgang in diesem Block nicht verwenden. Vermeiden Sie z. B. die Verwendung von Bezeichnern wie all oder last in Abfrageausdrücken.