Wyrażenia obliczeń

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 monads, monoidów, transformatorów monad i funktorów stosowania. Jednak w przeciwieństwie do innych języków (takich jak notacja w języku Haskell), nie są one powiązane z pojedynczą abstrakcją i nie polegają na makrach ani innych formach metaprogramowania w celu osiągnięcia wygodnej i kontekstowej składni.

Omówienie

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 obliczeń są tak proste, jak wykonywanie jednowątkowe. Przykłady obejmują:

  • Obliczenia niedeterministyczne
  • Obliczenia asynchroniczne
  • Obliczenia, które mają wpływ
  • Obliczenia generowania

Ogólnie rzecz biorąc, istnieją obliczenia kontekstowe , które należy wykonać w niektórych częściach aplikacji. Pisanie kodu kontekstowego może być trudne, ponieważ można łatwo "wyciekać" obliczeń poza danym kontekstem bez abstrakcji, aby zapobiec temu. 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 tym formularzu builder-expr jest nazwą typu konstruktora, który definiuje wyrażenie obliczeniowe i cexper jest treścią wyrażenia obliczeniowego wyrażenia. 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 jest cukrem składniowym do użycia, let! po którym następuje dopasowanie wzorca w 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 za pomocą letpolecenia , nie uzyskasz wyniku wyrażenia obliczeniowego. Zamiast tego należy powiązać wartość niezrealizowanego wywołania tego wyrażenia obliczeniowego. Użyj polecenia let! , aby powiązać z wynikiem.

let! element jest zdefiniowany przez element członkowski Bind(x, f) w typie konstruktora.

and!

Słowo and! kluczowe umożliwia powiązanie wyników wielu wywołań wyrażeń obliczeniowych w wydajny sposób.

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

Użycie serii let! ... let! ... wymusza ponowne wykonywanie kosztownych powiązań, dlatego użycie let! ... and! ... powinno być używane podczas wiązania wyników wielu wyrażeń obliczeniowych.

and! element jest definiowany MergeSources(x1, x2) głównie przez element członkowski w typie konstruktora.

Opcjonalnie można zdefiniować, MergeSourcesN(x1, x2 ..., xN) aby zmniejszyć liczbę węzłów tuplingu i BindN(x1, x2 ..., xN, f), lub BindNReturn(x1, x2, ..., xN, f) można je zdefiniować w celu efektywnego powiązania wyników wyrażenia obliczeniowego bez łączenia węzłów.

do!

Słowo do! kluczowe służy do wywoływania wyrażenia obliczeniowego, które zwraca unittyp podobny do -like (zdefiniowany przez Zero element członkowski 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! element jest definiowany przez element członkowski Bind(x, f) w typie konstruktora, w którym f tworzy element unit.

yield

Słowo yield kluczowe służy do zwracania wartości z wyrażenia obliczeniowego, aby można było go użyć jako elementu 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 warunkowo, po prostu pominięcie słowa kluczowego może zrobić:

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 w miarę iterated.

yield element jest definiowany Yield(x) przez element członkowski w typie konstruktora, gdzie x element jest zwracany.

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 obliczeniu wyrażenie obliczeniowe wywoływane przez yield! element będzie miało zwrócone elementy o wartości jeden po jednym, spłaszczając wynik.

yield! element jest definiowany przez element członkowski YieldFrom(x) w typie konstruktora, gdzie x jest kolekcją wartości.

W przeciwieństwie do yieldelementu 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 yieldelementu 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 element jest definiowany przez element członkowski Return(x) w typie konstruktora, gdzie x jest elementem do opakowania. W przypadku let! ... return użycia BindReturn(x, f) można użyć w celu zwiększenia wydajności.

return!

Słowo return! kluczowe zdaje sobie sprawę z wartości wyrażenia obliczeniowego i opakowuje typ odpowiadający 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! element jest definiowany przez element członkowski ReturnFrom(x) w typie konstruktora, gdzie x jest innym wyrażeniem obliczeniowym.

match!

Słowo match! kluczowe umożliwia wbudowane wywołanie innego wyrażenia obliczeniowego i dopasowania wzorca na jego wynik:

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

W przypadku wywoływania wyrażenia match!obliczeniowego za pomocą polecenia będzie on zdawał sobie sprawę z wyniku wywołania, takiego jak 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! wyrażeń obliczeniowych i do! w wyrażeniach obliczeniowych.
BindN (M<'T1> * M<'T2> * ... * M<'TN> * ('T1 * 'T2 ... * 'TN -> M<'U>)) -> M<'U> Wywoływane do wydajnego let! i and! wyrażeń obliczeniowych bez scalania danych wejściowych.

np. Bind3, Bind4.
Delay (unit -> M<'T>) -> Delayed<'T> Opakowuje wyrażenie obliczeniowe jako funkcję. Delayed<'T> może być dowolnym typem, często M<'T> używanym lub unit -> M<'T> używanym. 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.
BindReturn (M<'T1> * ('T1 -> 'T2)) -> M<'T2> Wywoływanie wydajnego let! ... return wyrażenia obliczeniowego.
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.

np. 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.

np. MergeSources3, MergeSources4.
Run Delayed<'T> -> M<'T> lub

M<'T> -> 'T
Wykonuje wyrażenie obliczeniowe.
Combine M<'T> * Delayed<'T> -> M<'T> lub

M<unit> * M<'T> -> M<'T>
Wywołana do sekwencjonowania w wyrażeniach obliczeniowych.
For seq<'T> * ('T -> M<'U>) -> M<'U> lub

seq<'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.
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 członkowskiego jako cudzysłów. Przekształca wszystkie wystąpienia obliczeń w cudzysłów.

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 jednej konstrukcji może zostać przekazany do następnej.

Wiele funkcji używa wyniku jako argumentuDelay: Run, , WhileTryWith, TryFinallyi Combine. Typ Delayed<'T> to zwracany typ Delay i w związku z tym parametr do 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>. Zobacz tutaj , aby uzyskać bardziej szczegółowy wygląd.

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 wzorców, których można używać w wyrażeniach obliczeniowych, takich jak implementacje kociołowe niektórych metod konstruktora.

/// 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 konstruktorów przy użyciu nowych operacji niestandardowych

Jeśli masz już klasę konstruktora, jego operacje niestandardowe można rozszerzyć spoza tej klasy konstruktora. Rozszerzenia muszą być zadeklarowane w modułach. Przestrzenie nazw nie mogą zawierać elementów członkowskich rozszerzenia z wyjątkiem tego samego pliku i tej samej grupy deklaracji przestrzeni nazw, w której jest zdefiniowany typ.

W poniższym przykładzie pokazano rozszerzenie istniejącej FSharp.Linq.QueryBuilder klasy.

open System
open FSharp.Linq

type QueryBuilder with

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

Operacje niestandardowe mogą być przeciążone. Aby uzyskać więcej informacji, zobacz F# RFC FS-1056 — Zezwalaj 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.

Zobacz też