Dela via


Kodofferter

Den här artikeln beskriver kodcitat, en språkfunktion som gör att du kan generera och arbeta med F#-koduttryck programmatiskt. Med den här funktionen kan du generera ett abstrakt syntaxträd som representerar F#-kod. Det abstrakta syntaxträdet kan sedan bläddras igenom och bearbetas enligt programmets behov. Du kan till exempel använda trädet för att generera F#-kod eller generera kod på något annat språk.

Citerade uttryck

Ett citerat uttryck är ett F#-uttryck i koden som avgränsas på ett sådant sätt att det inte kompileras som en del av programmet, utan i stället kompileras till ett objekt som representerar ett F#-uttryck. Du kan markera ett citerat uttryck på något av två sätt: antingen med typinformation eller utan typinformation. Om du vill inkludera typinformation använder du symbolerna <@ och @> avgränsar det citerade uttrycket. Om du inte behöver typinformation använder du symbolerna <@@ och @@>. Följande kod visar inskrivna och otypade citattecken.

open Microsoft.FSharp.Quotations
// A typed code quotation.
let expr : Expr<int> = <@ 1 + 1 @>
// An untyped code quotation.
let expr2 : Expr = <@@ 1 + 1 @@>

Det går snabbare att bläddra i ett stort uttrycksträd om du inte inkluderar typinformation. Den resulterande typen av ett uttryck som citeras med de inskrivna symbolerna är Expr<'T>, där typparametern har typen av uttryck som bestäms av F#-kompilatorns typinferensalgoritm. När du använder kodofferter utan typinformation är typen av det citerade uttrycket den icke-generiska typen Expr. Du kan anropa egenskapen Raw i den inskrivna Expr klassen för att hämta det otypade Expr objektet.

Det finns olika statiska metoder som gör att du kan generera F#-uttrycksobjekt programmatiskt i Expr klassen utan att använda citerade uttryck.

En kodcitat måste innehålla ett fullständigt uttryck. För en let bindning behöver du till exempel både definitionen av det bundna namnet och ett annat uttryck som använder bindningen. I utförlig syntax är detta ett uttryck som följer nyckelordet in . På den översta nivån i en modul är detta bara nästa uttryck i modulen, men i en offert krävs det uttryckligen.

Därför är följande uttryck inte giltigt.

// Not valid:
// <@ let f x = x + 1 @>

Men följande uttryck är giltiga.

// Valid:
<@ let f x = x + 10 in f 20 @>
// Valid:
<@
    let f x = x + 10
    f 20
@>

Om du vill utvärdera F#-offerter måste du använda F#-offertutvärderingen. Det ger stöd för att utvärdera och köra F#-uttrycksobjekt.

F#-citattecken behåller också information om typbegränsningar. Ta följande som exempel:

open FSharp.Linq.RuntimeHelpers

let eval q = LeafExpressionConverter.EvaluateQuotation q

let inline negate x = -x
// val inline negate: x: ^a ->  ^a when  ^a : (static member ( ~- ) :  ^a ->  ^a)

<@ negate 1.0 @>  |> eval

Begränsningen som genereras av inline funktionen behålls i kodofferten. Funktionens negate angivna formulär kan nu utvärderas.

Expr-typ

En instans av typen Expr representerar ett F#-uttryck. Både de generiska och icke-generiska Expr typerna dokumenteras i dokumentationen för F#-biblioteket. Mer information finns i FSharp.Quotations Namespace och Quotations.Expr Class.

Spliceringsoperatorer

Med splicering kan du kombinera literala kodofferter med uttryck som du har skapat programmatiskt eller från en annan kodcitat. Med % operatorerna och %% kan du lägga till ett F#-uttrycksobjekt i en kodcitat. Du använder operatorn % för att infoga ett skrivet uttrycksobjekt i en typad offert. Du använder operatorn %% för att infoga ett otypat uttrycksobjekt i en otypad offert. Båda operatorerna är unary-prefixoperatorer. expr Om är ett otypat uttryck av typen Exprär följande kod giltig.

<@@ 1 + %%expr @@>

Och om expr är ett typskrivet citattecken Expr<int>är följande kod giltig.

<@ 1 + %expr @>

Exempel 1

beskrivning

I följande exempel visas hur kodofferter används för att placera F#-kod i ett uttrycksobjekt och sedan skriva ut F#-koden som representerar uttrycket. En funktion println definieras som innehåller en rekursiv funktion print som visar ett F#-uttrycksobjekt (av typen Expr) i ett eget format. Det finns flera aktiva mönster i modulerna FSharp.Quotations.Patterns och FSharp.Quotations.DerivedPatterns som kan användas för att analysera uttrycksobjekt. Det här exemplet innehåller inte alla möjliga mönster som kan visas i ett F#-uttryck. Ett okänt mönster utlöser en matchning till jokerteckenmönstret (_) och återges med hjälp ToString av metoden, som på Expr typen låter dig veta det aktiva mönstret som ska läggas till i matchningsuttrycket.

Code

module Print
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns
open Microsoft.FSharp.Quotations.DerivedPatterns

let println expr =
    let rec print expr =
        match expr with
        | Application(expr1, expr2) ->
            // Function application.
            print expr1
            printf " "
            print expr2
        | SpecificCall <@@ (+) @@> (_, _, exprList) ->
            // Matches a call to (+). Must appear before Call pattern.
            print exprList.Head
            printf " + "
            print exprList.Tail.Head
        | Call(exprOpt, methodInfo, exprList) ->
            // Method or module function call.
            match exprOpt with
            | Some expr -> print expr
            | None -> printf "%s" methodInfo.DeclaringType.Name
            printf ".%s(" methodInfo.Name
            if (exprList.IsEmpty) then printf ")" else
            print exprList.Head
            for expr in exprList.Tail do
                printf ","
                print expr
            printf ")"
        | Int32(n) ->
            printf "%d" n
        | Lambda(param, body) ->
            // Lambda expression.
            printf "fun (%s:%s) -> " param.Name (param.Type.ToString())
            print body
        | Let(var, expr1, expr2) ->
            // Let binding.
            if (var.IsMutable) then
                printf "let mutable %s = " var.Name
            else
                printf "let %s = " var.Name
            print expr1
            printf " in "
            print expr2
        | PropertyGet(_, propOrValInfo, _) ->
            printf "%s" propOrValInfo.Name
        | String(str) ->
            printf "%s" str
        | Value(value, typ) ->
            printf "%s" (value.ToString())
        | Var(var) ->
            printf "%s" var.Name
        | _ -> printf "%s" (expr.ToString())
    print expr
    printfn ""


let a = 2

// exprLambda has type "(int -> int)".
let exprLambda = <@ fun x -> x + 1 @>
// exprCall has type unit.
let exprCall = <@ a + 1 @>

println exprLambda
println exprCall
println <@@ let f x = x + 10 in f 10 @@>

Output

fun (x:System.Int32) -> x + 1
a + 1
let f = fun (x:System.Int32) -> x + 10 in f 10

Exempel 2

beskrivning

Du kan också använda de tre aktiva mönstren i ExprShape-modulen för att korsa uttrycksträd med färre aktiva mönster. De här aktiva mönstren kan vara användbara när du vill korsa ett träd, men du behöver inte all information i de flesta noderna. När du använder dessa mönster matchar alla F#-uttryck något av följande tre mönster: ShapeVar om uttrycket är en variabel, ShapeLambda om uttrycket är ett lambda-uttryck eller ShapeCombination om uttrycket är något annat. Om du passerar ett uttrycksträd med hjälp av de aktiva mönstren som i föregående kodexempel måste du använda många fler mönster för att hantera alla möjliga F#-uttryckstyper, och koden blir mer komplex. Mer information finns i ExprShape.ShapeVar|ShapeLambda|ShapeCombination Aktivt mönster.

Följande kodexempel kan användas som grund för mer komplexa blädderingar. I den här koden skapas ett uttrycksträd för ett uttryck som omfattar ett funktionsanrop, add. Det aktiva mönstret SpecificCall används för att identifiera alla anrop till add i uttrycksträdet. Det här aktiva mönstret tilldelar anropets argument till exprList värdet. I det här fallet finns det bara två, så dessa hämtas och funktionen anropas rekursivt på argumenten. Resultatet infogas i en kodcitat som representerar ett anrop till mul med hjälp av splice-operatorn (%%). Funktionen println från föregående exempel används för att visa resultatet.

Koden i de andra aktiva mönstergrenarna återskapar bara samma uttrycksträd, så den enda ändringen i det resulterande uttrycket är ändringen från add till mul.

Code

module Module1
open Print
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.DerivedPatterns
open Microsoft.FSharp.Quotations.ExprShape

let add x y = x + y
let mul x y = x * y

let rec substituteExpr expression =
    match expression with
    | SpecificCall <@@ add @@> (_, _, exprList) ->
        let lhs = substituteExpr exprList.Head
        let rhs = substituteExpr exprList.Tail.Head
        <@@ mul %%lhs %%rhs @@>
    | ShapeVar var -> Expr.Var var
    | ShapeLambda (var, expr) -> Expr.Lambda (var, substituteExpr expr)
    | ShapeCombination(shapeComboObject, exprList) ->
        RebuildShapeCombination(shapeComboObject, List.map substituteExpr exprList)

let expr1 = <@@ 1 + (add 2 (add 3 4)) @@>
println expr1
let expr2 = substituteExpr expr1
println expr2

Output

1 + Module1.add(2,Module1.add(3,4))
1 + Module1.mul(2,Module1.mul(3,4))

Se även