Berekeningsexpressies

Berekeningsexpressies in F# bieden een handige syntaxis voor het schrijven van berekeningen die kunnen worden gesequentieerd en gecombineerd met behulp van besturingsstroomconstructies en bindingen. Afhankelijk van het soort berekeningsexpressie kunnen ze worden beschouwd als een manier om monads, monoids, monadtransformaties en applicatieve functors uit te drukken. In tegenstelling tot andere talen (zoals do-notatie in Haskell), zijn ze echter niet gekoppeld aan één abstractie en zijn ze niet afhankelijk van macro's of andere vormen van metaprogrammering om een handige en contextgevoelige syntaxis te bereiken.

Overzicht

Berekeningen kunnen vele vormen aannemen. De meest voorkomende vorm van berekening is uitvoering met één thread. Dit is eenvoudig te begrijpen en te wijzigen. Niet alle vormen van berekeningen zijn echter net zo eenvoudig als uitvoering met één thread. Enkele voorbeelden:

  • Niet-deterministische berekeningen
  • Asynchrone berekeningen
  • Effectvolle berekeningen
  • Generatieve berekeningen

Over het algemeen zijn er contextgevoelige berekeningen die u moet uitvoeren in bepaalde onderdelen van een toepassing. Het schrijven van contextgevoelige code kan lastig zijn, omdat het eenvoudig is om berekeningen buiten een bepaalde context te lekken zonder abstracties om dit te voorkomen. Deze abstracties zijn vaak lastig om zelf te schrijven. Daarom heeft F# een gegeneraliseerde manier om zogenaamde rekenexpressies uit te voeren.

Berekeningsexpressies bieden een uniform syntaxis- en abstractiemodel voor het coderen van contextgevoelige berekeningen.

Elke berekeningsexpressie wordt ondersteund door een opbouwfunctietype . Het opbouwfunctietype definieert de bewerkingen die beschikbaar zijn voor de berekeningsexpressie. Zie Een nieuw type berekeningsexpressie maken, waarin wordt getoond hoe u een aangepaste berekeningsexpressie maakt.

Syntaxisoverzicht

Alle rekenexpressies hebben de volgende vorm:

builder-expr { cexper }

In dit formulier builder-expr is dit de naam van een opbouwfunctietype dat de berekeningsexpressie definieert en cexper is dit de hoofdtekst van de berekeningsexpressie. De rekenexpressiecode kan er bijvoorbeeld async als volgt uitzien:

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

        let processedData = processData data

        return processedData
    }

Er is een speciale, aanvullende syntaxis beschikbaar in een berekeningsexpressie, zoals wordt weergegeven in het vorige voorbeeld. De volgende expressieformulieren zijn mogelijk met rekenexpressies:

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

Elk van deze trefwoorden en andere standaard F#-trefwoorden zijn alleen beschikbaar in een berekeningsexpressie als deze zijn gedefinieerd in het type backing builder. De enige uitzondering hierop is match!, dat is zelf syntactische suiker voor het gebruik van let! gevolgd door een patroonovereenkomst op het resultaat.

Het opbouwfunctietype is een object dat speciale methoden definieert die bepalen hoe de fragmenten van de rekenexpressie worden gecombineerd; Dat wil gezegd: de methoden bepalen hoe de rekenexpressie zich gedraagt. Een andere manier om een opbouwklasse te beschrijven, is door te zeggen dat u hiermee de werking van veel F#-constructies, zoals lussen en bindingen, kunt aanpassen.

let!

Het let! trefwoord verbindt het resultaat van een aanroep naar een andere rekenexpressie met een naam:

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

Als u de aanroep koppelt aan een berekeningsexpressie, letkrijgt u niet het resultaat van de berekeningsexpressie. In plaats daarvan hebt u de waarde van de niet-gerealiseerde aanroep naar die rekenexpressie gebonden. Gebruik let! dit om te binden aan het resultaat.

let! wordt gedefinieerd door het Bind(x, f) lid op het type opbouwfunctie.

and!

Met and! het trefwoord kunt u de resultaten van meerdere aanroepen van berekeningsexpressies op een performante manier binden.

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

Het gebruik van een reeks krachten voor het opnieuw uitvoeren van dure bindingen, dus gebruik let! ... and! ... moet worden gebruikt bij het binden van de resultaten van talloze let! ... let! ... rekenexpressies.

and! wordt voornamelijk gedefinieerd door het MergeSources(x1, x2) lid op het type opbouwfunctie.

U kunt desgewenst MergeSourcesN(x1, x2 ..., xN) definiëren om het aantal tupling-knooppunten te verminderen en BindN(x1, x2 ..., xN, f), of BindNReturn(x1, x2, ..., xN, f) kan worden gedefinieerd om de resultaten van de berekeningsexpressie efficiënt te binden zonder knooppunten te koppelen.

do!

Het do! trefwoord is voor het aanroepen van een rekenexpressie die een unit-like-type retourneert (gedefinieerd door het Zero lid in de opbouwfunctie):

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

Voor de asynchrone werkstroom is Async<unit>dit type. Voor andere berekeningsexpressies is CExpType<unit>het type waarschijnlijk .

do! wordt gedefinieerd door het Bind(x, f) lid op het type opbouwfunctie, waarbij f een unit.

yield

Het yield trefwoord is bedoeld voor het retourneren van een waarde uit de berekeningsexpressie, zodat deze als een IEnumerable<T>:

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

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

In de meeste gevallen kan dit worden weggelaten door bellers. De meest voorkomende manier om weg te laten yield is met de -> operator:

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

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

Voor complexere expressies die veel verschillende waarden kunnen opleveren, en misschien voorwaardelijk, kunt u het trefwoord weglaten:

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

Net als bij het trefwoord rendement in C# wordt elk element in de berekeningsexpressie teruggegeven terwijl het wordt ge curseerd.

yield wordt gedefinieerd door het Yield(x) lid op het type opbouwfunctie, waar x het item moet worden teruggegeven.

yield!

Het yield! trefwoord is bedoeld voor het platmaken van een verzameling waarden uit een rekenexpressie:

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

Wanneer de berekeningsexpressie wordt aangeroepen yield! , worden de items één voor één geretourneerd, waardoor het resultaat wordt afgevlakt.

yield! wordt gedefinieerd door het YieldFrom(x) lid van het opbouwfunctietype, waarbij x een verzameling waarden is.

In tegenstelling tot yield, yield! moet expliciet worden opgegeven. Het gedrag is niet impliciet in berekeningsexpressies.

return

Het return trefwoord verpakt een waarde in het type dat overeenkomt met de berekeningsexpressie. Afgezien van berekeningsexpressies die worden yieldgebruikt, wordt deze gebruikt om een berekeningsexpressie te 'voltooien':

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 wordt gedefinieerd door het Return(x) lid van het opbouwfunctietype, waar x het item moet worden verpakt. Voor let! ... return gebruik BindReturn(x, f) kan worden gebruikt voor verbeterde prestaties.

return!

Het return! trefwoord realiseert de waarde van een berekeningsexpressie en verpakt dat resulteert in het type dat overeenkomt met de rekenexpressie:

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

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

return! wordt gedefinieerd door het ReturnFrom(x) lid van het opbouwfunctietype, waarbij een x andere rekenexpressie is.

match!

Met match! het trefwoord kunt u een aanroep naar een andere berekeningsexpressie en patroonovereenkomst inline plaatsen op het resultaat:

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

Wanneer u een berekeningsexpressie aanroept match!, wordt het resultaat van de aanroep als let!. Dit wordt vaak gebruikt bij het aanroepen van een berekeningsexpressie waarbij het resultaat een optioneel resultaat is.

Ingebouwde rekenexpressies

De F#-kernbibliotheek definieert vier ingebouwde rekenexpressies: Reeksexpressies, Async-expressies, Taakexpressies en Query-expressies.

Een nieuw type berekeningsexpressie maken

U kunt de kenmerken van uw eigen berekeningsexpressies definiëren door een opbouwklasse te maken en bepaalde speciale methoden voor de klasse te definiëren. De opbouwfunctieklasse kan desgewenst de methoden definiëren zoals vermeld in de volgende tabel.

In de volgende tabel worden methoden beschreven die kunnen worden gebruikt in een werkstroombouwerklasse.

Methode Typische handtekening(en) Beschrijving
Bind M<'T> * ('T -> M<'U>) -> M<'U> Aangeroepen voor let! en do! in berekeningsexpressies.
BindN (M<'T1> * M<'T2> * ... * M<'TN> * ('T1 * 'T2 ... * 'TN -> M<'U>)) -> M<'U> Wordt aangeroepen voor efficiënte let! en and! in berekeningsexpressies zonder invoer samen te voegen.

bijvoorbeeld Bind3, Bind4.
Delay (unit -> M<'T>) -> Delayed<'T> Verpakt een berekeningsexpressie als een functie. Delayed<'T> kan elk type zijn, vaak M<'T> of unit -> M<'T> worden gebruikt. De standaard implementatie retourneert een M<'T>.
Return 'T -> M<'T> return Aangeroepen in berekeningsexpressies.
ReturnFrom M<'T> -> M<'T> return! Aangeroepen in berekeningsexpressies.
BindReturn (M<'T1> * ('T1 -> 'T2)) -> M<'T2> Aangeroepen voor een efficiënte let! ... return berekeningsexpressie.
BindNReturn (M<'T1> * M<'T2> * ... * M<'TN> * ('T1 * 'T2 ... * 'TN -> M<'U>)) -> M<'U> Wordt aangeroepen voor efficiënte let! ... and! ... return berekeningsexpressies zonder invoer samen te voegen.

bijvoorbeeld Bind3Return, Bind4Return.
MergeSources (M<'T1> * M<'T2>) -> M<'T1 * 'T2> and! Aangeroepen in berekeningsexpressies.
MergeSourcesN (M<'T1> * M<'T2> * ... * M<'TN>) -> M<'T1 * 'T2 * ... * 'TN> and! Aangeroepen in rekenexpressies, maar verbetert de efficiëntie door het aantal tupling-knooppunten te verminderen.

bijvoorbeeld MergeSources3, MergeSources4.
Run Delayed<'T> -> M<'T> of

M<'T> -> 'T
Hiermee wordt een berekeningsexpressie uitgevoerd.
Combine M<'T> * Delayed<'T> -> M<'T> of

M<unit> * M<'T> -> M<'T>
Wordt aangeroepen voor sequentiëren in berekeningsexpressies.
For seq<'T> * ('T -> M<'U>) -> M<'U> of

seq<'T> * ('T -> M<'U>) -> seq<M<'U>>
Aangeroepen voor for...do expressies in berekeningsexpressies.
TryFinally Delayed<'T> * (unit -> unit) -> M<'T> Aangeroepen voor try...finally expressies in berekeningsexpressies.
TryWith Delayed<'T> * (exn -> M<'T>) -> M<'T> Aangeroepen voor try...with expressies in berekeningsexpressies.
Using 'T * ('T -> M<'U>) -> M<'U> when 'T :> IDisposable Aangeroepen voor use bindingen in berekeningsexpressies.
While (unit -> bool) * Delayed<'T> -> M<'T>Of

(unit -> bool) * Delayed<unit> -> M<unit>
Aangeroepen voor while...do expressies in berekeningsexpressies.
Yield 'T -> M<'T> Aangeroepen voor yield expressies in berekeningsexpressies.
YieldFrom M<'T> -> M<'T> Aangeroepen voor yield! expressies in berekeningsexpressies.
Zero unit -> M<'T> Aangeroepen voor lege else vertakkingen van if...then expressies in rekenexpressies.
Quote Quotations.Expr<'T> -> Quotations.Expr<'T> Geeft aan dat de berekeningsexpressie wordt doorgegeven aan het Run lid als een aanhalingsteken. Hiermee worden alle exemplaren van een berekening omgezet in een offerte.

Veel van de methoden in een opbouwklasse gebruiken en retourneren een M<'T> constructie, wat doorgaans een afzonderlijk gedefinieerd type is dat het type berekeningen dat wordt gecombineerd, bijvoorbeeld Async<'T> voor asynchrone expressies en Seq<'T> voor reekswerkstromen, aangeeft. Met de handtekeningen van deze methoden kunnen ze worden gecombineerd en genest met elkaar, zodat het werkstroomobject dat wordt geretourneerd vanuit de ene constructie, kan worden doorgegeven aan de volgende.

Veel functies gebruiken het resultaat als Delay argument: Run, While, TryWith, , TryFinallyen Combine. Het Delayed<'T> type is het retourtype van Delay en dus de parameter voor deze functies. Delayed<'T> kan een willekeurig type zijn dat niet hoeft te worden gerelateerd aan M<'T>; vaak M<'T> of (unit -> M<'T>) worden gebruikt. De standaard implementatie is M<'T>. Zie hier voor een uitgebreider overzicht.

De compiler, wanneer een berekeningsexpressie wordt geparseerd, vertaalt de expressie in een reeks geneste functie-aanroepen met behulp van de methoden in de voorgaande tabel en de code in de berekeningsexpressie. De geneste expressie heeft de volgende vorm:

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

In de bovenstaande code worden de aanroepen naar Run en Delay weggelaten als ze niet zijn gedefinieerd in de opbouwfunctieklasse voor berekeningsexpressies. De hoofdtekst van de berekeningsexpressie, hier aangeduid als {{ cexpr }}, wordt omgezet in verdere aanroepen naar de methoden van de opbouwfunctieklasse. Dit proces wordt recursief gedefinieerd volgens de vertalingen in de volgende tabel. Code binnen dubbele haken {{ ... }} blijft vertaald, expr vertegenwoordigt een F#-expressie en cexpr vertegenwoordigt een rekenexpressie.

Expression Vertaling
{{ 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 de vorige tabel other-expr wordt een expressie beschreven die anders niet in de tabel wordt vermeld. Een opbouwfunctieklasse hoeft niet alle methoden te implementeren en alle vertalingen in de vorige tabel te ondersteunen. Deze constructies die niet zijn geïmplementeerd, zijn niet beschikbaar in berekeningsexpressies van dat type. Als u bijvoorbeeld het use trefwoord in uw berekeningsexpressies niet wilt ondersteunen, kunt u de definitie weglaten in Use uw opbouwklasse.

In het volgende codevoorbeeld ziet u een berekeningsexpressie die een berekening inkapselt als een reeks stappen die één stap tegelijk kunnen worden geëvalueerd. Een gediscrimineerd samenvoegingstype, OkOrExceptioncodeert de foutstatus van de expressie zoals die tot nu toe is geëvalueerd. Deze code demonstreert verschillende typische patronen die u kunt gebruiken in uw berekeningsexpressies, zoals standaard implementaties van enkele van de opbouwmethoden.

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

Een berekeningsexpressie heeft een onderliggend type dat door de expressie wordt geretourneerd. Het onderliggende type kan een berekend resultaat of een vertraagde berekening vertegenwoordigen die kan worden uitgevoerd, of het kan een manier bieden om een bepaald type verzameling te herhalen. In het vorige voorbeeld was Eventually<_>het onderliggende type . Voor een reeksexpressie is System.Collections.Generic.IEnumerable<T>het onderliggende type . Voor een query-expressie is System.Linq.IQueryablehet onderliggende type . Voor een asynchrone expressie is Asynchet onderliggende type . Het Async object vertegenwoordigt het werk dat moet worden uitgevoerd om het resultaat te berekenen. U roept Async.RunSynchronously bijvoorbeeld aan om een berekening uit te voeren en het resultaat te retourneren.

Aangepaste bewerkingen

U kunt een aangepaste bewerking definiëren voor een berekeningsexpressie en een aangepaste bewerking gebruiken als operator in een berekeningsexpressie. U kunt bijvoorbeeld een queryoperator opnemen in een query-expressie. Wanneer u een aangepaste bewerking definieert, moet u het rendement en voor methoden in de berekeningsexpressie definiëren. Als u een aangepaste bewerking wilt definiëren, plaatst u deze in een opbouwklasse voor de berekeningsexpressie en past u vervolgens de CustomOperationAttribute. Dit kenmerk gebruikt een tekenreeks als argument. Dit is de naam die moet worden gebruikt in een aangepaste bewerking. Deze naam komt aan het begin van de opening accolade van de rekenexpressie binnen het bereik. Gebruik daarom geen id's met dezelfde naam als een aangepaste bewerking in dit blok. Vermijd bijvoorbeeld het gebruik van id's, zoals all of last in query-expressies.

Bestaande opbouwfuncties uitbreiden met nieuwe aangepaste bewerkingen

Als u al een opbouwfunctieklasse hebt, kunnen de aangepaste bewerkingen van buiten deze opbouwklasse worden uitgebreid. Extensies moeten worden gedeclareerd in modules. Naamruimten mogen geen extensieleden bevatten, behalve in hetzelfde bestand en dezelfde naamruimtedeclaratiegroep waarin het type is gedefinieerd.

In het volgende voorbeeld ziet u de extensie van de bestaande FSharp.Linq.QueryBuilder klasse.

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

Aangepaste bewerkingen kunnen overbelast worden. Zie F# RFC FS-1056 - Overbelasting van aangepaste trefwoorden in berekeningsexpressies toestaan voor meer informatie.

Berekeningsexpressies efficiënt compileren

F#-berekeningsexpressies die de uitvoering onderbreken, kunnen worden gecompileerd naar zeer efficiënte statusmachines door zorgvuldig gebruik te maken van een functie op laag niveau, zogenaamde hervatbare code. Hervatbare code wordt beschreven in F# RFC FS-1087 en wordt gebruikt voor taakexpressies.

F#-berekeningsexpressies die synchroon zijn (dat wil gezegd, ze onderbreken de uitvoering niet) kunnen ook worden gecompileerd naar efficiënte statusmachines met behulp van inlinefuncties , waaronder het InlineIfLambda kenmerk. Voorbeelden worden gegeven in F# RFC FS-1098.

Lijstexpressies, matrixexpressies en reeksexpressies krijgen een speciale behandeling door de F#-compiler om te zorgen voor het genereren van krachtige code.

Zie ook