Freigeben über


Code-Anführungszeichen

In diesem Artikel werden Codeangebote beschrieben, ein Sprachfeature, mit dem Sie F#-Codeausdrücke programmgesteuert generieren und arbeiten können. Mit diesem Feature können Sie eine abstrakte Syntaxstruktur generieren, die F#-Code darstellt. Die abstrakte Syntaxstruktur kann dann entsprechend den Anforderungen Ihrer Anwendung durchlaufen und verarbeitet werden. Sie können z. B. die Struktur verwenden, um F#-Code zu generieren oder Code in einer anderen Sprache zu generieren.

Zitierte Ausdrücke

Ein zitierter Ausdruck ist ein F#-Ausdruck in Ihrem Code, der so getrennt ist, dass er nicht als Teil des Programms kompiliert wird, sondern stattdessen in ein Objekt kompiliert wird, das einen F#-Ausdruck darstellt. Sie können einen an zitierten Ausdruck auf eine von zwei Arten markieren: entweder mit Typinformationen oder ohne Typinformationen. Wenn Sie Typinformationen einschließen möchten, verwenden Sie die Symbole <@ , und @> trennen Sie den an zitierten Ausdruck. Wenn Sie keine Typinformationen benötigen, verwenden Sie die Symbole <@@ und @@>. Der folgende Code zeigt eingegebene und nicht eingegebene Anführungszeichen.

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

Das Durchlaufen einer großen Ausdrucksstruktur ist schneller, wenn Sie keine Typinformationen einschließen. Der resultierende Typ eines Ausdrucks, der mit den eingegebenen Symbolen zitiert wird, lautet Expr<'T>, wobei der Typparameter den Typ des Ausdrucks hat, der durch den F#-Compilertyp-Ableitungsalgorithmus bestimmt wird. Wenn Sie Code-Anführungszeichen ohne Typinformationen verwenden, ist der Typ des in Anführungszeichen angegebenen Ausdrucks der nicht generische Ausdruck. Sie können die Raw-Eigenschaft für die typisierte Expr Klasse aufrufen, um das nicht typisierte Expr Objekt abzurufen.

Es gibt verschiedene statische Methoden, mit denen Sie F#-Ausdrucksobjekte programmgesteuert in der Expr Klasse generieren können, ohne an zitierte Ausdrücke zu verwenden.

Ein Codeangebot muss einen vollständigen Ausdruck enthalten. Für eine let Bindung benötigen Sie z. B. sowohl die Definition des gebundenen Namens als auch einen anderen Ausdruck, der die Bindung verwendet. In ausführlicher Syntax ist dies ein Ausdruck, der dem in Schlüsselwort folgt. Auf der obersten Ebene in einem Modul ist dies nur der nächste Ausdruck im Modul, aber in einem Anführungszeichen ist es explizit erforderlich.

Daher ist der folgende Ausdruck ungültig.

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

Die folgenden Ausdrücke sind jedoch gültig.

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

Zum Auswerten von F#-Anführungszeichen müssen Sie den F#-Anführungs-Evaluator verwenden. Es bietet Unterstützung für das Auswerten und Ausführen von F#-Ausdrucksobjekten.

F#-Anführungszeichen behalten auch Typeinschränkungsinformationen bei. Betrachten Sie das folgenden Beispiel:

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

Die von der inline Funktion generierte Einschränkung wird in der Codevorführung beibehalten. Das negate Formular der Funktion kann jetzt ausgewertet werden.

Ausdruckstyp

Eine Instanz des Expr Typs stellt einen F#-Ausdruck dar. Sowohl die generischen als auch die nicht generischen Expr Typen werden in der F#-Bibliotheksdokumentation dokumentiert. Weitere Informationen finden Sie unter "FSharp.Quotations Namespace " und " Quotations.Expr"-Klasse.

Splicing-Operatoren

Mithilfe von Splicing können Sie Literalcode-Anführungszeichen mit Ausdrücken kombinieren, die Sie programmgesteuert oder aus einem anderen Codeangebot erstellt haben. Mit den % Operatoren können %% Sie einem Codeangebot ein F#-Ausdrucksobjekt hinzufügen. Sie verwenden den % Operator, um ein typisiertes Ausdrucksobjekt in ein typisiertes Anführungszeichen einzufügen. Sie verwenden den %% Operator, um ein nicht typisiertes Ausdrucksobjekt in ein nicht typisiertes Anführungszeichen einzufügen. Beide Operatoren sind unäre Präfixoperatoren. expr Wenn es sich also um einen nicht typisierten Ausdruck handeltExpr, ist der folgende Code gültig.

<@@ 1 + %%expr @@>

Und wenn expr es sich um ein typiertes Anführungszeichen handelt Expr<int>, ist der folgende Code gültig.

<@ 1 + %expr @>

Beispiel 1

BESCHREIBUNG

Das folgende Beispiel veranschaulicht die Verwendung von Codeanführungszeichen zum Einfügen von F#-Code in ein Ausdrucksobjekt und druckt dann den F#-Code, der den Ausdruck darstellt. Eine Funktion println ist definiert, die eine rekursive Funktion print enthält, die ein F#-Ausdrucksobjekt (vom Typ Expr) in einem anzeigefreundlichen Format anzeigt. Es gibt mehrere aktive Muster in den Modulen FSharp.Quotations.Patterns und FSharp.Quotations.DerivedPatterns , die zum Analysieren von Ausdrucksobjekten verwendet werden können. Dieses Beispiel enthält nicht alle möglichen Muster, die in einem F#-Ausdruck angezeigt werden können. Jedes nicht erkannte Muster löst eine Übereinstimmung mit dem Wildcardmuster (_) aus und wird mithilfe der Methode gerendert, mit der ToString Sie im Expr Typ das aktive Muster kennen, das dem Übereinstimmungsausdruck hinzugefügt werden soll.

Programmcode

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

Beispiel 2

BESCHREIBUNG

Sie können auch die drei aktiven Muster im ExprShape-Modul verwenden, um Ausdrucksstrukturen mit weniger aktiven Mustern zu durchlaufen. Diese aktiven Muster können nützlich sein, wenn Sie eine Struktur durchlaufen möchten, aber sie benötigen nicht alle Informationen in den meisten Knoten. Wenn Sie diese Muster verwenden, stimmt jeder F#-Ausdruck mit einem der folgenden drei Muster überein: ShapeVar Wenn der Ausdruck eine Variable ist, ShapeLambda wenn der Ausdruck ein Lambda-Ausdruck ist oder ShapeCombination wenn der Ausdruck etwas anderes ist. Wenn Sie eine Ausdrucksstruktur durchlaufen, indem Sie die aktiven Muster wie im vorherigen Codebeispiel verwenden, müssen Sie viele weitere Muster verwenden, um alle möglichen F#-Ausdruckstypen zu verarbeiten, und Ihr Code ist komplexer. Weitere Informationen finden Sie unter ExprShape.ShapeVar|ShapeLambda|ShapeCombination Active Pattern.

Das folgende Codebeispiel kann als Grundlage für komplexere Traversale verwendet werden. In diesem Code wird eine Ausdrucksstruktur für einen Ausdruck erstellt, der einen Funktionsaufruf umfasst. add Das " SpecificCall "-Aktive Muster wird verwendet, um jeden Aufruf add in der Ausdrucksstruktur zu erkennen. Dieses aktive Muster weist die Argumente des Aufrufs dem exprList Wert zu. In diesem Fall gibt es nur zwei, sodass diese herausgezogen werden und die Funktion rekursiv für die Argumente aufgerufen wird. Die Ergebnisse werden in ein Code-Anführungszeichen eingefügt, das einen Aufruf mul mithilfe des Komplizenoperators (%%) darstellt. Die println Funktion aus dem vorherigen Beispiel wird verwendet, um die Ergebnisse anzuzeigen.

Der Code in den anderen aktiven Musterzweigen generiert nur die gleiche Ausdrucksstruktur, sodass die einzige Änderung des resultierenden Ausdrucks die Änderung von add zu mul" ist.

Programmcode

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

Siehe auch