Codezitate

In diesem Artikel werden Codezitate beschrieben, eine Sprachfunktion, mit der Sie F#-Codeausdrücke programmgesteuert erstellen und verwenden können. Mit dieser Funktion können Sie eine abstrakte Syntaxstruktur erstellen, die für F#-Code steht. Diese abstrakte Syntaxstruktur kann dann entsprechend den Anforderungen Ihrer Anwendung durchlaufen und verarbeitet werden. Beispielsweise können Sie mit der Struktur F#-Code oder Code in einer anderen Sprache erstellen.

Zitierte Ausdrücke

Ein zitierter Ausdruck ist ein F#-Ausdruck in Ihrem Code, der so abgetrennt wird, dass er nicht als Teil Ihres Programms, sondern in ein Objekt kompiliert wird, das für einen F#-Ausdruck steht. Sie können einen zitierten Ausdruck auf zwei Weisen kennzeichnen: entweder mit Typinformationen oder ohne Typinformationen. Wenn Sie die Typinformationen angeben möchten, trennen Sie den zitierten Ausdruck mit den Symbolen <@ und @> ab. Wenn Sie die Typinformationen nicht benötigen, verwenden Sie die Symbole <@@ und @@>. Der folgende Code zeigt typisierte und nicht typisierte Zitate.

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 geht schneller, wenn Sie keine Typinformationen angeben. Der resultierende Typ eines Ausdrucks, der mit den typisierten Symbolen zitiert wird, ist Expr<'T>, wobei der Typparameter den Ausdruckstyp aufweist, der durch den Typrückschluss-Algorithmus des F#-Compilers bestimmt wird. Wenn Sie Codezitate ohne Typinformationen verwenden, ist der Typ des zitierten Ausdrucks der nicht generische Typ Expr. Sie können die Eigenschaft Raw auf der typisierten Expr-Klasse aufrufen, um das nicht typisierte Expr-Objekt abzurufen.

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

Ein Codezitat muss einen vollständigen Ausdruck enthalten. Bei einer let-Bindung benötigen Sie z. B. die Definition des gebundenen Namens und einen weiteren Ausdruck, der diese Bindung verwendet. In ausführlicher Syntax ist dies ein Ausdruck, der nach dem Schlüsselwort in folgt. Auf der höchsten Ebene in einem Modul ist dies einfach der nächste Ausdruck im Modul, in einem Zitat dagegen explizit erforderlich.

Daher ist der folgenden Ausdruck nicht gültig.

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

Die folgenden Ausdrücke dagegen sind gültig.

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

Zum Auswerten von F#-Zitaten müssen Sie den F# Quotation Evaluator verwenden. Er unterstützt die Auswertung und Ausführung von F#-Ausdrucksobjekten.

F#-Zitate behalten auch Informationen zu Typeinschränkungen 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 im Codezitat beibehalten. Die zitierte Form der negate-Funktion kann ausgewertet werden.

Expr-Typ

Eine Instanz des Expr-Typs steht für einen F#-Ausdruck. Die generischen und die nicht generischen Expr-Typen sind in der Dokumentation zur F#-Bibliothek beschrieben. Weitere Informationen finden Sie unter Namespace von FSharp.Quotations und Quotations.Expr-Klasse.

Splicing-Operatoren

Mit Splicing können Sie literale Codezitate mit programmgesteuert erstellten Ausdrücken oder Ausdrücken von anderen Codezitaten kombinieren. Mit den Operatoren % und %% können Sie ein F#-Ausdrucksobjekt in ein Codezitat einfügen. Um ein typisiertes Ausdrucksobjekt in ein typisiertes Zitat einzufügen, verwenden Sie den Operator %. Mit dem Operator %% können Sie ein nicht typisiertes Ausdrucksobjekt in ein nicht typisiertes Zitat einfügen. Beide Operatoren sind unäre Präfixoperatoren. Wenn expr ein nicht typisierter Ausdruck des Typs Expr ist, ist der folgende Code somit gültig.

<@@ 1 + %%expr @@>

Und wenn expr ein typisiertes Zitat des Typs Expr<int> ist, ist der folgende Code gültig.

<@ 1 + %expr @>

Beispiel 1

Beschreibung

Das folgende Beispiel zeigt, wie Sie mit Codezitaten F#-Code in ein Ausdrucksobjekt einfügen und dann den F#-Code ausgeben, der für den Ausdruck steht. Die Funktion println wird definiert, die eine rekursive Funktion print enthält, welche ein F#-Ausdrucksobjekt (des Typs Expr) in einem anzeigefreundlichen Format anzeigt. In den Modulen FSharp.Quotations.Patterns und FSharp.Quotations.DerivedPatterns gibt es mehrere aktive Muster, mit denen Sie Ausdrucksobjekte analysieren können. Dieses Beispiel enthält nicht alle möglichen Muster, die in einem F#-Ausdruck vorkommen können. Jedes nicht erkannte Muster löst eine Übereinstimmung mit dem Platzhaltermuster (_) aus und wird mit der Methode ToString gerendert. Anhand des Expr-Typs können Sie erkennen, welches aktive Muster Sie dem Übereinstimmungsausdruck hinzufügen müssen.

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

Beispiel 2

Beschreibung

Sie können auch die drei aktiven Muster im Modul ExprShape verwenden, um Ausdrucksstrukturen mit weniger aktiven Mustern zu durchlaufen. Diese aktiven Muster können hilfreich sein, wenn Sie eine Struktur durchlaufen möchten, aber von den meisten Knoten nicht alle Informationen benötigen. 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 Lambdaausdruck ist, oder ShapeCombination in allen anderen Fällen. Wenn Sie eine Ausdrucksstruktur mit den aktiven Mustern durchlaufen, wie im vorherigen Codebeispiel gezeigt, müssen Sie viele weitere Muster verwenden, um alle möglichen F#-Ausdruckstypen zu verarbeiten, was die Komplexität Ihres Codes erhöht. Weitere Informationen finden Sie unter ExprShape.ShapeVar|ShapeLambda|ShapeCombination – aktive Muster.

Das folgende Codebeispiel kann als Basis für komplexere Durchläufe verwendet werden. In diesem Code wird eine Ausdrucksstruktur für einen Ausdruck erstellt, der einen Funktionsaufruf, add, enthält. Mit dem aktiven Muster SpecificCall werden alle Aufrufe von add in der Ausdrucksstruktur erkannt. Dieses aktive Muster weist die Argumente des Aufrufs dem Wert exprList zu. In diesem Fall gibt es nur zwei. Sie werden also herausgezogen, und die Funktion wird rekursiv auf den Argumenten aufgerufen. Die Ergebnisse werden mit dem Splicing-Operator (%%) in ein Codezitat eingefügt, das für einen Aufruf von mul steht. Mit der Funktion println aus dem vorherigen Beispiel werden die Ergebnisse angezeigt.

Der Code in den anderen aktiven Musterzweigen erstellt dieselbe Ausdrucksstruktur erneut. Die einzige Änderung des resultierenden Ausdrucks ist daher die Änderung von add in 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))

Siehe auch