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