Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Wyrażenia obliczeniowe w języku F# zapewniają wygodną składnię do pisania obliczeń, które można sekwencjonować i łączyć przy użyciu konstrukcji i powiązań przepływu sterowania. W zależności od rodzaju wyrażenia obliczeniowego można je traktować jako sposób wyrażania monad, monoidów, transformatorów monad i funkcji aplikatywnych. Jednak w przeciwieństwie do innych języków (takich jak do-notacja w Haskell), nie są one powiązane z pojedynczą abstrakcją i nie polegają na makrach ani innych formach metaprogramowania, aby uzyskać wygodną i kontekstowo wrażliwą składnię.
Przegląd
Obliczenia mogą mieć wiele form. Najczęstszą formą obliczeń jest wykonywanie jednowątkowe, które jest łatwe do zrozumienia i zmodyfikowania. Jednak nie wszystkie formy przetwarzania są tak proste jak wykonanie jednowątkowe. Oto kilka przykładów:
- Obliczenia niedeterministyczne
- Obliczenia asynchroniczne
- Obliczenia efektywne
- Obliczenia generatywne
Ogólnie rzecz biorąc, istnieją obliczenia kontekstowe , które należy wykonać w niektórych częściach aplikacji. Pisanie kodu wrażliwego na kontekst może być trudne, ponieważ łatwo "ujawniać" obliczenia poza danym kontekstem, jeśli nie ma abstrakcji, które by temu zapobiegały. Te abstrakcje są często trudne do samodzielnego zapisu, dlatego język F# ma uogólniony sposób wykonywania tak zwanych wyrażeń obliczeniowych.
Wyrażenia obliczeniowe oferują jednolity model składni i abstrakcji na potrzeby kodowania obliczeń kontekstowych.
Każde wyrażenie obliczeniowe jest wspierane przez typ konstruktora . Typ konstruktora definiuje operacje dostępne dla wyrażenia obliczeniowego. Zobacz Tworzenie nowego typu wyrażenia obliczeniowego, w którym pokazano, jak utworzyć niestandardowe wyrażenie obliczeniowe.
Omówienie składni
Wszystkie wyrażenia obliczeniowe mają następującą formę:
builder-expr { cexper }
W tej formie, builder-expr jest nazwą typu konstruktora, który definiuje wyrażenie obliczeniowe, a cexper jest treścią wyrażenia obliczeniowego. Na przykład async kod wyrażenia obliczeniowego może wyglądać następująco:
let fetchAndDownload url =
async {
let! data = downloadData url
let processedData = processData data
return processedData
}
Istnieje specjalna, dodatkowa składnia dostępna w wyrażeniu obliczeniowym, jak pokazano w poprzednim przykładzie. Następujące formularze wyrażeń są możliwe za pomocą wyrażeń obliczeniowych:
expr { let! ... }
expr { and! ... }
expr { do! ... }
expr { yield ... }
expr { yield! ... }
expr { return ... }
expr { return! ... }
expr { match! ... }
Każde z tych słów kluczowych i inne standardowe słowa kluczowe języka F# są dostępne tylko w wyrażeniu obliczeniowym, jeśli zostały zdefiniowane w typie konstruktora kopii zapasowej. Jedynym wyjątkiem od tego jest match!, który sam w sobie jest cukrem składniowym dla użycia let!, po którym następuje dopasowanie wzorca na wyniku.
Typ konstruktora to obiekt, który definiuje specjalne metody, które określają sposób łączenia fragmentów wyrażenia obliczeniowego; oznacza to, że jego metody kontrolują zachowanie wyrażenia obliczeniowego. Innym sposobem opisania klasy konstruktora jest stwierdzenie, że umożliwia dostosowanie operacji wielu konstrukcji języka F#, takich jak pętle i powiązania.
let!
Słowo let! kluczowe wiąże wynik wywołania z innym wyrażeniem obliczeniowym z nazwą:
let doThingsAsync url =
async {
let! data = getDataAsync url
...
}
Jeśli powiążesz wywołanie z wyrażeniem obliczeniowym przy użyciu let, nie uzyskasz wyniku wyrażenia obliczeniowego. Zamiast tego powiążesz wartość wywołania niezrealizowanego z tym wyrażeniem obliczeniowym. Użyj let! do powiązania z wynikiem.
let! jest zdefiniowany przez członka Bind(x, f) dla typu konstruktora.
and!
Słowo and! kluczowe umożliwia powiązanie wyników wielu wywołań wyrażeń obliczeniowych wydajniej. To słowo kluczowe umożliwia stosowanie wyrażeń obliczeniowych, które zapewniają inny model obliczeniowy od standardowego podejścia monadycznego.
let doThingsAsync url =
async {
let! data = getDataAsync url
and! moreData = getMoreDataAsync anotherUrl
and! evenMoreData = getEvenMoreDataAsync someUrl
...
}
Użycie serii let! ... let! ... wykona obliczenia sekwencyjnie, nawet jeśli są niezależne. Z kolei wskazuje, let! ... and! ... że obliczenia są niezależne, co pozwala na stosowanie kombinacji. Ta niezależność umożliwia autorom wyrażeń obliczeniowych:
- Wydajniejsze wykonywanie obliczeń.
- Może uruchamiać obliczenia równolegle.
- Kumuluj wyniki bez niepotrzebnych zależności sekwencyjnych.
Ograniczenie polega na tym, że obliczenia połączone z and! programem nie mogą zależeć od wyników wcześniej powiązanych wartości w tym samym let!/and! łańcuchu. Ta kompromis umożliwia korzystanie z zalet wydajności.
and! jest głównie definiowany przez członka MergeSources(x1, x2) w typie konstruktora.
Opcjonalnie można zdefiniować MergeSourcesN(x1, x2 ..., xN), aby zmniejszyć liczbę węzłów krotek, oraz BindN(x1, x2 ..., xN, f) lub BindNReturn(x1, x2, ..., xN, f), aby efektywnie powiązać wyniki wyrażenia obliczeniowego bez węzłów krotek.
Aby uzyskać więcej informacji na temat wyrażeń obliczeniowych stosowania, zobacz Wyrażenia obliczeniowe stosowania w języku F# 5 i F# RFC FS-1063.
do!
Słowo kluczowe do! służy do wywoływania wyrażenia obliczeniowego, które zwraca typ podobny do unit, zdefiniowany przez członka Zero w konstruktorze.
let doThingsAsync data url =
async {
do! submitData data url
...
}
W przypadku przepływu pracy asynchronicznego ten typ to Async<unit>. W przypadku innych wyrażeń obliczeniowych typ może mieć wartość CExpType<unit>.
do! jest definiowane przez członka Bind(x, f) w typie budowniczego, gdzie f generuje unit.
yield
Słowo kluczowe yield służy do zwracania wartości z wyrażenia obliczeniowego, aby mogła być wykorzystana jako IEnumerable<T>.
let squares =
seq {
for i in 1..10 do
yield i * i
}
for sq in squares do
printfn $"%d{sq}"
W większości przypadków można go pominąć przez osoby wywołujące. Najczęstszym sposobem pominięcia yield jest użycie -> operatora :
let squares =
seq {
for i in 1..10 -> i * i
}
for sq in squares do
printfn $"%d{sq}"
W przypadku bardziej złożonych wyrażeń, które mogą zwracać wiele różnych wartości, a być może także działać warunkowo, wystarczy po prostu pominąć słowo kluczowe.
let weekdays includeWeekend =
seq {
"Monday"
"Tuesday"
"Wednesday"
"Thursday"
"Friday"
if includeWeekend then
"Saturday"
"Sunday"
}
Podobnie jak w przypadku słowa kluczowego yield w języku C#, każdy element w wyrażeniu obliczeniowym jest zwracany podczas iteracji.
yield jest definiowany przez członka typu konstruktora Yield(x), gdzie x jest elementem do zwrócenia.
yield!
Słowo yield! kluczowe służy do spłaszczania kolekcji wartości z wyrażenia obliczeniowego:
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
Po wykonaniu, wyrażenie obliczeniowe wywołane przez yield! będzie zwracało elementy jeden po drugim, co spowoduje spłaszczenie wyniku.
yield! element jest definiowany przez element członkowski YieldFrom(x) w typie konstruktora, gdzie x jest kolekcją wartości.
W przeciwieństwie do yield, yield! należy jawnie określić. Jego zachowanie nie jest niejawne w wyrażeniach obliczeniowych.
return
Słowo return kluczowe opakowuje wartość w typie odpowiadającym wyrażeniu obliczeniowemu. Oprócz wyrażeń obliczeniowych korzystających z yield, jest on używany do "ukończenia" wyrażenia obliczeniowego:
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 jest definiowany przez członka Return(x) w typie konstruktora, gdzie x jest obiektem do zawinięcia. W przypadku let! ... return można użyć BindReturn(x, f) w celu poprawy wydajności.
return!
Słowo kluczowe return! realizuje wartość wyrażenia obliczeniowego i zamyka ten wynik w typie odpowiadającym wyrażeniu obliczeniowemu.
let req = // 'req' is of type 'Async<data>'
async {
return! fetch url
}
// 'result' is of type 'data'
let result = Async.RunSynchronously req
return! jest definiowany przez członkowski element ReturnFrom(x) na typie konstruktora, gdzie x to inne wyrażenie obliczeniowe.
match!
Słowo kluczowe match! umożliwia wbudowane wywołanie innego wyrażenia obliczeniowego i dopasowanie wzorca do jego wyniku.
let doThingsAsync url =
async {
match! callService url with
| Some data -> ...
| None -> ...
}
W przypadku wywołania wyrażenia obliczeniowego match!, wynik zostanie obliczony w sposób opisany przez let!. Jest to często używane podczas wywoływania wyrażenia obliczeniowego, w którym wynik jest opcjonalny.
Wbudowane wyrażenia obliczeniowe
Podstawowa biblioteka języka F# definiuje cztery wbudowane wyrażenia obliczeniowe: Wyrażenia sekwencji, Wyrażenia asynchroniczne, Wyrażenia zadań i Wyrażenia zapytań.
Tworzenie nowego typu wyrażenia obliczeniowego
Można zdefiniować cechy własnych wyrażeń obliczeniowych, tworząc klasę konstruktora i definiując pewne specjalne metody w klasie. Klasa konstruktora może opcjonalnie zdefiniować metody wymienione w poniższej tabeli.
W poniższej tabeli opisano metody, które mogą być używane w klasie konstruktora przepływu pracy.
| Metoda | Typowe podpisy | Opis |
|---|---|---|
Bind |
M<'T> * ('T -> M<'U>) -> M<'U> |
Wywoływane dla let! i do! w wyrażeniach obliczeniowych. |
BindN |
(M<'T1> * M<'T2> * ... * M<'TN> * ('T1 * 'T2 ... * 'TN -> M<'U>)) -> M<'U> |
Wymagane do wydajnego let! i and! wyrażeń obliczeniowych bez łączenia danych wejściowych.na przykład , Bind3. Bind4 |
Delay |
(unit -> M<'T>) -> Delayed<'T> |
Opakowuje wyrażenie obliczeniowe jako funkcję.
Delayed<'T> może być dowolnym typem, często używa się M<'T> lub unit -> M<'T>. Domyślna implementacja zwraca wartość M<'T>. |
Return |
'T -> M<'T> |
Wywoływane w return wyrażeniach obliczeniowych. |
ReturnFrom |
M<'T> -> M<'T> |
Wywoływane w return! wyrażeniach obliczeniowych. |
ReturnFromFinal |
M<'T> -> M<'T> |
Jeśli jest obecny, wywołaj metodę return! i do! w pozycji wywołania końcowego. |
BindReturn |
(M<'T1> * ('T1 -> 'T2)) -> M<'T2> |
Potrzeba wydajnego let! ... return w wyrażeniach obliczeniowych. |
BindNReturn |
(M<'T1> * M<'T2> * ... * M<'TN> * ('T1 * 'T2 ... * 'TN -> M<'U>)) -> M<'U> |
Wywoływanie wydajnego let! ... and! ... return w wyrażeniach obliczeniowych bez scalania danych wejściowych.na przykład , Bind3Return. Bind4Return |
MergeSources |
(M<'T1> * M<'T2>) -> M<'T1 * 'T2> |
Wywoływane w and! wyrażeniach obliczeniowych. |
MergeSourcesN |
(M<'T1> * M<'T2> * ... * M<'TN>) -> M<'T1 * 'T2 * ... * 'TN> |
Wywoływane w and! wyrażeniach obliczeniowych, ale zwiększa wydajność, zmniejszając liczbę węzłów tuplingu.na przykład , MergeSources3. MergeSources4 |
Run |
Delayed<'T> -> M<'T> lubM<'T> -> 'T |
Wykonuje wyrażenie obliczeniowe. |
Combine |
M<'T> * Delayed<'T> -> M<'T> lubM<unit> * M<'T> -> M<'T> |
Wywołanie do sekwencjonowania w wyrażeniach obliczeniowych. |
For |
seq<'T> * ('T -> M<'U>) -> M<'U> lubseq<'T> * ('T -> M<'U>) -> seq<M<'U>> |
Wywoływane dla for...do wyrażeń obliczeniowych. |
TryFinally |
Delayed<'T> * (unit -> unit) -> M<'T> |
Wywoływane dla try...finally wyrażeń obliczeniowych. |
TryWith |
Delayed<'T> * (exn -> M<'T>) -> M<'T> |
Wywoływane dla try...with wyrażeń obliczeniowych. |
Using |
'T * ('T -> M<'U>) -> M<'U> when 'T :> IDisposable |
Wywoływane dla use powiązań w wyrażeniach obliczeniowych. |
While |
(unit -> bool) * Delayed<'T> -> M<'T>lub(unit -> bool) * Delayed<unit> -> M<unit> |
Wywoływane dla while...do wyrażeń obliczeniowych. |
Yield |
'T -> M<'T> |
Wywoływane dla yield wyrażeń obliczeniowych. |
YieldFrom |
M<'T> -> M<'T> |
Wywoływane dla yield! wyrażeń obliczeniowych. |
YieldFromFinal |
M<'T> -> M<'T> |
Jeśli jest obecny, wywołaj metodę , aby yield! w pozycji wywołania końcowego i w przypadku pozycji wywołania końcowego do! jako rezerwowego dla ReturnFromFinal |
Zero |
unit -> M<'T> |
Wywoływane dla pustych elseif...then gałęzi wyrażeń w wyrażeniach obliczeniowych. |
Quote |
Quotations.Expr<'T> -> Quotations.Expr<'T> |
Wskazuje, że wyrażenie obliczeniowe jest przekazywane do elementu Run jako cytat. Przekształca wszystkie przypadki obliczeń w cytaty. |
Wiele metod w klasie konstruktora używa i zwraca konstrukcję M<'T> , która jest zazwyczaj oddzielnie zdefiniowanym typem, który charakteryzuje rodzaj połączonych obliczeń, na przykład Async<'T> dla wyrażeń asynchronicznych i Seq<'T> dla przepływów pracy sekwencji. Podpisy tych metod umożliwiają ich łączenie i zagnieżdżanie ze sobą, dzięki czemu obiekt przepływu pracy zwrócony z jednego konstruktu może zostać przekazany do następnego.
Wiele funkcji używa wyniku Delay jako argumentu: Run, While, TryWith, TryFinally i Combine. Typ Delayed<'T> jest typem zwracanym funkcji Delay oraz parametrem dla tych funkcji.
Delayed<'T> może być dowolnym typem, który nie musi być powiązany z M<'T>; często M<'T> lub (unit -> M<'T>) są używane. Domyślna implementacja to M<'T>. Aby uzyskać bardziej dogłębne zrozumienie, zobacz Omówienie ograniczeń typu.
Kompilator, gdy analizuje wyrażenie obliczeniowe, tłumaczy wyrażenie na serię zagnieżdżonych wywołań funkcji przy użyciu metod w poprzedniej tabeli i kodu w wyrażeniu obliczeniowym. Wyrażenie zagnieżdżone ma następującą postać:
builder.Run(builder.Delay(fun () -> {{ cexpr }}))
W powyższym kodzie wywołania metody Run i Delay są pomijane, jeśli nie są zdefiniowane w klasie konstruktora wyrażeń obliczeniowych. Treść wyrażenia obliczeniowego, tutaj oznaczona jako {{ cexpr }}, jest tłumaczona na dalsze wywołania metod klasy konstruktora. Ten proces jest definiowany rekursywnie zgodnie z tłumaczeniami w poniższej tabeli. Kod w nawiasach podwójnych {{ ... }} pozostaje tłumaczony, expr reprezentuje wyrażenie języka F# i cexpr reprezentuje wyrażenie obliczeniowe.
| Wyrażenie | Tłumaczenie |
|---|---|
{{ 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() |
W poprzedniej tabeli opisano wyrażenie, other-expr które nie jest inaczej wymienione w tabeli. Klasa konstruktora nie musi implementować wszystkich metod i obsługiwać wszystkie tłumaczenia wymienione w poprzedniej tabeli. Te konstrukcje, które nie są zaimplementowane, nie są dostępne w wyrażeniach obliczeniowych tego typu. Jeśli na przykład nie chcesz obsługiwać słowa kluczowego use w wyrażeniach obliczeniowych, możesz pominąć definicję Use w klasie konstruktora.
Poniższy przykład kodu przedstawia wyrażenie obliczeniowe, które hermetyzuje obliczenia jako serię kroków, które można ocenić jeden krok naraz. Dyskryminowany typ unii, OkOrException, koduje stan błędu wyrażenia zgodnie z oceną do tej pory. Ten kod przedstawia kilka typowych schematów, których można używać w wyrażeniach obliczeniowych, takich jak szablonowe implementacje niektórych metod budowniczych.
/// 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
Wyrażenie obliczeniowe ma typ bazowy, który zwraca wyrażenie. Typ bazowy może reprezentować obliczony wynik lub opóźnione obliczenia, które można wykonać, lub może zapewnić sposób iteracji za pośrednictwem pewnego typu kolekcji. W poprzednim przykładzie podstawowym typem był Eventually<_>. W przypadku wyrażenia sekwencji podstawowy typ to System.Collections.Generic.IEnumerable<T>. W przypadku wyrażenia zapytania podstawowy typ to System.Linq.IQueryable. W przypadku wyrażenia asynchronicznego podstawowy typ to Async. Obiekt Async reprezentuje pracę do wykonania w celu obliczenia wyniku. Na przykład wywołasz metodę Async.RunSynchronously , aby wykonać obliczenia i zwrócić wynik.
Operacje niestandardowe
Możesz zdefiniować operację niestandardową w wyrażeniu obliczeniowym i użyć operacji niestandardowej jako operatora w wyrażeniu obliczeniowym. Można na przykład uwzględnić operator zapytania w wyrażeniu zapytania. Podczas definiowania operacji niestandardowej należy zdefiniować metody Yield i For w wyrażeniu obliczeniowym. Aby zdefiniować operację niestandardową, umieść ją w klasie konstruktora dla wyrażenia obliczeniowego, a następnie zastosuj element CustomOperationAttribute. Ten atrybut przyjmuje ciąg jako argument, który jest nazwą, która ma być używana w operacji niestandardowej. Ta nazwa wchodzi w zakres na początku otwierającego nawiasu klamrowego wyrażenia obliczeniowego. W związku z tym nie należy używać identyfikatorów, które mają taką samą nazwę jak operacja niestandardowa w tym bloku. Na przykład unikaj używania identyfikatorów, takich jak all lub last w wyrażeniach zapytania.
Rozszerzanie istniejących budowniczych przy użyciu nowych operacji niestandardowych
Jeśli masz już klasę konstruktora, jego niestandardowe operacje można rozszerzać także poza tą klasą. Rozszerzenia muszą być zadeklarowane w modułach. Przestrzenie nazw mogą zawierać członków rozszerzeń tylko w tym samym pliku i tej samej grupie deklaracji przestrzeni nazw, w której zdefiniowany jest typ.
W poniższym przykładzie przedstawiono rozszerzenia istniejącej FSharp.Linq.QueryBuilder klasy.
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))
Operacje niestandardowe mogą być przeciążone. Aby uzyskać więcej informacji, zobacz F# RFC FS-1056 — Zezwolenie na przeciążenia niestandardowych słów kluczowych w wyrażeniach obliczeniowych.
Wydajne kompilowanie wyrażeń obliczeniowych
Wyrażenia obliczeniowe języka F#, które zawieszają wykonywanie, można skompilować do wysoce wydajnych maszyn stanowych za pomocą starannej funkcji niskiego poziomu nazywanej kodem wznawianym. Kod możliwy do wznowienia jest udokumentowany w języku F# RFC FS-1087 i używany do wyrażeń zadań.
Wyrażenia obliczeniowe języka F#, które są synchroniczne (czyli nie zawieszają wykonywania) mogą być również kompilowane do wydajnych maszyn stanowych przy użyciu wbudowanych funkcji , w tym atrybutu InlineIfLambda . Przykłady podano w F# RFC FS-1098.
Wyrażenia listy, wyrażenia tablicy i wyrażenia sekwencji są objęte specjalnym traktowaniem przez kompilator języka F#, aby zapewnić generowanie kodu o wysokiej wydajności.