Megosztás a következőn keresztül:


Számítási kifejezések

Az F# számítási kifejezései kényelmes szintaxist biztosítanak a számítások írásához, amelyek vezérlőfolyamat-szerkezetek és kötések használatával sorrendbe állíthatók és kombinálhatók. A számítási kifejezés típusától függően a monádok, monoidok, monad transzformátorok és applikatív funktorok kifejezésére is gondolhatunk. Más nyelvektől (például a Haskell do-notationtól ) eltérően azonban nem egyetlen absztrakcióhoz vannak kötve, és nem támaszkodnak makrókra vagy más metaprogramozási formákra a kényelmes és környezetfüggő szintaxis eléréséhez.

Áttekintés

A számítások számos formában használhatók. A számítás leggyakoribb formája az egyszálas végrehajtás, amely könnyen érthető és módosítható. Azonban nem minden számítási forma olyan egyszerű, mint az egyszálas végrehajtás. Néhány példa:

  • Nem determinisztikus számítások
  • Aszinkron számítások
  • Effektusos számítások
  • Generatív számítások

Általánosságban elmondható, hogy vannak környezetfüggő számítások, amelyeket az alkalmazás bizonyos részeiben kell elvégeznie. A környezetfüggő kód írása nehézkes lehet, mivel könnyen "kiszivárogtathatja" a számításokat egy adott környezeten kívül, absztrakciók nélkül, hogy megakadályozza ezt. Ezeket az absztrakciókat gyakran nehéz önállóan írni, ezért az F#-nak általános módja van az úgynevezett számítási kifejezések elvégzésére.

A számítási kifejezések egységes szintaxist és absztrakciós modellt kínálnak a környezetfüggő számítások kódolásához.

Minden számítási kifejezést egy szerkesztőtípus biztosít. A szerkesztő típusa határozza meg a számítási kifejezéshez elérhető műveleteket. Lásd: Új típusú számítási kifejezés létrehozása, amely bemutatja, hogyan hozhat létre egyéni számítási kifejezéseket.

Szintaxis áttekintése

Minden számítási kifejezés a következő űrlapot tartalmazza:

builder-expr { cexper }

Ebben a formában egy olyan szerkesztőtípus neve, builder-expr amely meghatározza a számítási kifejezést, és cexper a számítási kifejezés kifejezéstörzse. A számítási kifejezés kódja például async így nézhet ki:

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

        let processedData = processData data

        return processedData
    }

Egy számítási kifejezésben egy speciális, további szintaxis érhető el, ahogyan az előző példában is látható. Számítási kifejezésekkel a következő kifejezésűrlapok használhatók:

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

Ezek a kulcsszavak és egyéb standard F#-kulcsszavak csak akkor érhetők el számítási kifejezésekben, ha a háttérszerkesztő típusában vannak definiálva. Az egyetlen kivétel ez alól az match!, ami maga a szintaktikus cukor használata let! után egy minta egyezés az eredmény.

A szerkesztő típusa egy olyan objektum, amely speciális metódusokat határoz meg, amelyek szabályozzák a számítási kifejezés töredékeinek kombinálását; vagyis a metódusok szabályozzák a számítási kifejezés viselkedését. A szerkesztőosztály leírásának másik módja, ha azt mondjuk, hogy lehetővé teszi számos F#-szerkezet, például hurkok és kötések működésének testreszabását.

let!

A let! kulcsszó egy hívás eredményét egy másik számítási kifejezéshez köti egy névhez:

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

Ha a hívást egy számítási kifejezéshez letköti, akkor nem fogja megkapni a számítási kifejezés eredményét. Ehelyett a nem realizált hívás értékét az adott számítási kifejezéshez kötötte. Az eredményhez való kötéshez használható let! .

let! a szerkesztőtípus tagja határozza Bind(x, f) meg.

and!

A and! kulcsszó lehetővé teszi, hogy több számítási kifejezés hívásának eredményét performanszos módon kösse össze.

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

A drága kötések újrafuttatása erők sorozatával let! ... let! ... , ezért a használatot let! ... and! ... számos számítási kifejezés eredményeinek kötésekor kell használni.

and! elsősorban a MergeSources(x1, x2) szerkesztőtípushoz tartozó tag határozza meg.

Megadható úgy is, MergeSourcesN(x1, x2 ..., xN) hogy csökkentse a csonkoló csomópontok számát, és BindN(x1, x2 ..., xN, f)meg is határozható a BindNReturn(x1, x2, ..., xN, f) számítási kifejezések eredményeinek hatékony kötéséhez csomópontok hozzáadása nélkül.

do!

A do! kulcsszó egy olyan számítási kifejezés meghívására használható, amely -szerű típust unitad vissza (amelyet a szerkesztő tagja Zero határoz meg):

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

Az aszinkron munkafolyamat esetében ez a típus .Async<unit> Más számítási kifejezések esetében a típus valószínűleg az .CExpType<unit>

do! a szerkesztőtípus tagja határozza Bind(x, f) meg, ahol f egy unit.

yield

A yield kulcsszó arra szolgál, hogy visszaadjon egy értéket a számítási kifejezésből, hogy az felhasználható legyen IEnumerable<T>:

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

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

A legtöbb esetben a hívók kihagyhatják. A kihagyás yield leggyakoribb módja az operátorral -> :

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

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

Összetettebb kifejezések esetén, amelyek számos különböző értéket eredményeznek, és talán feltételesen is, egyszerűen kihagyják a kulcsszót:

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

A C#-ban használt hozam kulcsszóhoz hasonlóan a számítási kifejezés minden eleme vissza lesz engedett az iteráció során.

yield a szerkesztőtípus tagja határozza Yield(x) meg, ahol x az elem visszahozandó.

yield!

A yield! kulcsszó egy számítási kifejezésből származó értékek gyűjteményének összesimítására használható:

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

A kiértékeléskor a függvény által yield! hívott számítási kifejezés elemei egyenként lesznek visszaadva, és összesimítják az eredményt.

yield! a szerkesztőtípus tagja határozza YieldFrom(x) meg, ahol x értékek gyűjteménye van.

Eltérően yield, yield! explicit módon kell megadni. Viselkedése nem implicit a számítási kifejezésekben.

return

A return kulcsszó a számítási kifejezésnek megfelelő típusba csomagolja az értéket. A számítási kifejezések használatán yieldkívül egy számítási kifejezés "kiegészítésére" is használható:

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 a szerkesztőtípus tagja határozza Return(x) meg, ahol x az elem körbefut. A let! ... return használathoz BindReturn(x, f) jobb teljesítmény érhető el.

return!

A return! kulcsszó felismeri egy számítási kifejezés értékét, és olyan sortöréseket végez, amelyek a számítási kifejezésnek megfelelő típust eredményezik:

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

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

return! a szerkesztőtípus tagja határozza ReturnFrom(x) meg, ahol x egy másik számítási kifejezés.

match!

A match! kulcsszó lehetővé teszi egy másik számítási kifejezés és mintaegyeztetés meghívásának beírását az eredmény alapján:

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

Számítási kifejezés meghívásakor match!a függvény a következő hívás let!eredményét fogja felismerni: . Ezt gyakran használják olyan számítási kifejezések meghívásakor, ahol az eredmény nem kötelező.

Beépített számítási kifejezések

Az F#-magtár négy beépített számítási kifejezést határoz meg: Szekvenciakifejezéseket, Aszinkron kifejezéseket, Feladatkifejezéseket és Lekérdezési kifejezéseket.

Új típusú számítási kifejezés létrehozása

A saját számítási kifejezések jellemzőinek meghatározásához hozzon létre egy szerkesztőosztályt, és definiáljon bizonyos speciális metódusokat az osztályban. A szerkesztőosztály opcionálisan definiálhatja a metódusokat az alábbi táblázatban felsoroltak szerint.

Az alábbi táblázat a munkafolyamat-szerkesztő osztályban használható módszereket ismerteti.

Módszer Jellemző aláírás(ok) Leírás
Bind M<'T> * ('T -> M<'U>) -> M<'U> Számítási kifejezések meghívása let! és do! megadása.
BindN (M<'T1> * M<'T2> * ... * M<'TN> * ('T1 * 'T2 ... * 'TN -> M<'U>)) -> M<'U> Hatékony let! és and! számítási kifejezésekre van hívva a bemenetek egyesítése nélkül.

pl. Bind3, Bind4.
Delay (unit -> M<'T>) -> Delayed<'T> Függvényként körbefuttat egy számítási kifejezést. Delayed<'T> lehet bármilyen típusú, gyakran M<'T> használt vagy unit -> M<'T> használt. Az alapértelmezett implementáció egy M<'T>.
Return 'T -> M<'T> Számítási kifejezésekben hívjuk return meg.
ReturnFrom M<'T> -> M<'T> Számítási kifejezésekben hívjuk return! meg.
BindReturn (M<'T1> * ('T1 -> 'T2)) -> M<'T2> Hatékony let! ... return számítási kifejezésekre van hívva.
BindNReturn (M<'T1> * M<'T2> * ... * M<'TN> * ('T1 * 'T2 ... * 'TN -> M<'U>)) -> M<'U> let! ... and! ... return A számítási kifejezések hatékony egyesítését követeli meg a bemenetek egyesítése nélkül.

pl. Bind3Return, Bind4Return.
MergeSources (M<'T1> * M<'T2>) -> M<'T1 * 'T2> Számítási kifejezésekben hívjuk and! meg.
MergeSourcesN (M<'T1> * M<'T2> * ... * M<'TN>) -> M<'T1 * 'T2 * ... * 'TN> Számítási kifejezésekben való meghívás and! , de a kihasználatlan csomópontok számának csökkentésével javítja a hatékonyságot.

pl. MergeSources3, MergeSources4.
Run Delayed<'T> -> M<'T> vagy

M<'T> -> 'T
Számítási kifejezést hajt végre.
Combine M<'T> * Delayed<'T> -> M<'T> vagy

M<unit> * M<'T> -> M<'T>
A számítási kifejezésekben történő szekvenálást kéri.
For seq<'T> * ('T -> M<'U>) -> M<'U> vagy

seq<'T> * ('T -> M<'U>) -> seq<M<'U>>
A számítási kifejezések kifejezéseinek meghívása for...do .
TryFinally Delayed<'T> * (unit -> unit) -> M<'T> A számítási kifejezések kifejezéseinek meghívása try...finally .
TryWith Delayed<'T> * (exn -> M<'T>) -> M<'T> A számítási kifejezések kifejezéseinek meghívása try...with .
Using 'T * ('T -> M<'U>) -> M<'U> when 'T :> IDisposable A számítási kifejezések kötéseinek meghívása use .
While (unit -> bool) * Delayed<'T> -> M<'T>Vagy

(unit -> bool) * Delayed<unit> -> M<unit>
A számítási kifejezések kifejezéseinek meghívása while...do .
Yield 'T -> M<'T> A számítási kifejezések kifejezéseinek meghívása yield .
YieldFrom M<'T> -> M<'T> A számítási kifejezések kifejezéseinek meghívása yield! .
Zero unit -> M<'T> A számítási kifejezésekben lévő kifejezések üres else ágainak if...then meghívása.
Quote Quotations.Expr<'T> -> Quotations.Expr<'T> Azt jelzi, hogy a számítási kifejezés idézőjelként lesz átadva a Run tagnak. A számítás összes példányát idézőjelekké fordítja le.

A szerkesztőosztály számos metódusa használ és ad vissza egy M<'T> szerkezetet, amely általában egy külön definiált típus, amely az egyesítendő számítások típusát jellemzi, például Async<'T> az aszinkron kifejezésekhez és Seq<'T> a szekvencia-munkafolyamatokhoz. Ezeknek a metódusoknak az aláírásai lehetővé teszik, hogy egyesíthetők és egymásba ágyazódjanak, így az egyik szerkezetből visszaadott munkafolyamat-objektum átadható a következőnek.

Számos függvény argumentumként használja az eredményt Delay : Run, While, TryWith, TryFinallyés Combine. A Delayed<'T> típus a függvények visszatérési Delay típusa, következésképpen a paraméter. Delayed<'T> tetszőleges típus lehet, amelyet nem kell összekapcsolni M<'T>; gyakran M<'T> vagy (unit -> M<'T>) gyakran használják. Az alapértelmezett implementáció a következő M<'T>: . Részletesebb megjelenésért lásd itt .

A fordító egy számítási kifejezés elemzésekor beágyazott függvényhívások sorozatává fordítja le a kifejezést az előző táblázatban szereplő metódusok és a számítási kifejezés kódjának használatával. A beágyazott kifejezés a következő formában található:

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

A fenti kódban a hívásokat Run Delay a rendszer kihagyja, ha nincsenek definiálva a számítási kifejezéskészítő osztályban. A számítási kifejezés törzse, amely itt a következőképpen van jelölve {{ cexpr }}, a szerkesztőosztály metódusainak további hívásaiba lesz lefordítva. Ez a folyamat rekurzív módon van definiálva az alábbi táblázatban található fordításoknak megfelelően. A két zárójelen {{ ... }} belüli kód továbbra is lefordítandó, egy F#-kifejezést jelöl, expr és cexpr egy számítási kifejezést jelöl.

Expression Fordítás
{{ 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()

Az előző táblázatban egy olyan kifejezést ismertet, other-expr amely egyébként nem szerepel a táblázatban. A szerkesztőosztálynak nem kell implementálnia az összes metódust, és támogatnia kell az előző táblázatban felsorolt összes fordítást. Ezek a nem implementált szerkezetek nem érhetők el az ilyen típusú számítási kifejezésekben. Ha például nem szeretné támogatni a kulcsszót a use számítási kifejezésekben, kihagyhatja a szerkesztőosztály definícióját Use .

Az alábbi példakód egy számítási kifejezést mutat be, amely lépéssorozatként foglalja magában a számítást, amely egyszerre egy lépésben értékelhető ki. A diszkriminált egyesítő típus az OkOrExceptioneddig kiértékelt kifejezés hibaállapotát kódolja. Ez a kód számos tipikus mintát mutat be, amelyeket a számítási kifejezésekben használhat, például a szerkesztő metódusok egy részének kazántáblás implementációit.

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

A számítási kifejezéseknek van egy mögöttes típusa, amelyet a kifejezés ad vissza. Az alapul szolgáló típus jelentheti a kiszámított eredményt vagy a késleltetett számítást, amely végrehajtható, vagy valamilyen típusú gyűjteményen keresztüli iterálást is lehetővé tehet. Az előző példában a mögöttes típus a következő volt Eventually<_>: . Szekvenciakifejezés esetén a mögöttes típus a következő System.Collections.Generic.IEnumerable<T>: . Lekérdezési kifejezés esetén a mögöttes típus a következő System.Linq.IQueryable: . Aszinkron kifejezés esetén a mögöttes típus a .Async Az Async objektum az eredmény kiszámításához végrehajtandó munkát jelöli. Például egy számítás végrehajtására hív Async.RunSynchronously meg, és visszaadja az eredményt.

Egyéni műveletek

Egy számítási kifejezésen egyéni műveletet definiálhat, és egyéni műveletet használhat operátorként egy számítási kifejezésben. Egy lekérdezési operátort például belefoglalhat egy lekérdezési kifejezésbe. Egyéni művelet definiálásakor meg kell határoznia a Hozam és a For metódust a számítási kifejezésben. Egyéni művelet definiálásához tegye azt egy szerkesztőosztályba a számítási kifejezéshez, majd alkalmazza a CustomOperationAttribute. Ez az attribútum argumentumként egy sztringet vesz fel, amely egy egyéni műveletben használandó név. Ez a név a számítási kifejezés nyitó kapcsos zárójelének elején lép a hatókörbe. Ezért ne használjon olyan azonosítókat, amelyek neve megegyezik a blokk egyéni műveletével. Kerülje például az olyan azonosítók használatát, mint a all lekérdezési kifejezések vagy last a lekérdezési kifejezések.

Meglévő szerkesztők kiterjesztése új egyéni műveletekkel

Ha már rendelkezik szerkesztőosztálysal, az egyéni műveletei kiterjeszthetők ezen a szerkesztőosztályon kívülről is. A bővítményeket modulokban kell deklarálni. A névterek nem tartalmazhatnak bővítménytagokat, kivéve ugyanabban a fájlban és ugyanabban a névtérdeklarációs csoportban, amelyben a típus definiálva van.

Az alábbi példa a meglévő FSharp.Linq.QueryBuilder osztály kiterjesztését mutatja be.

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

Az egyéni műveletek túlterhelhetők. További információ: F# RFC FS-1056 – Egyéni kulcsszavak túlterhelésének engedélyezése számítási kifejezésekben.

Számítási kifejezések hatékony összeállítása

A végrehajtást felfüggesztő F#-számítási kifejezések rendkívül hatékony állapotú gépekre fordíthatók az újra felhasználható kód nevű alacsony szintű funkció gondos használatával. Az újra felhasználható kód az F# RFC FS-1087-ben van dokumentálva, és feladatkifejezésekhez használatos.

A szinkron (azaz a végrehajtást nem felfüggesztő) F# számítási kifejezések a hatékony állapotú gépekre fordíthatók úgy, hogy beágyazott függvényeket használnak, beleértve az InlineIfLambda attribútumot is. Ilyenek például az F# RFC FS-1098.

A listakifejezéseket, a tömbkifejezéseket és a szekvenciakifejezéseket az F#-fordító speciálisan kezeli a nagy teljesítményű kód generálásának biztosítása érdekében.

Lásd még