Code-aanhalingstekens
In dit artikel worden codecitaten beschreven, een taalfunctie waarmee u programmatisch F#-code-expressies kunt genereren en ermee kunt werken. Met deze functie kunt u een abstracte syntaxisstructuur genereren die F#-code vertegenwoordigt. De abstracte syntaxisstructuur kan vervolgens worden doorkruist en verwerkt volgens de behoeften van uw toepassing. U kunt bijvoorbeeld de structuur gebruiken om F#-code te genereren of code te genereren in een andere taal.
Expressies tussen aan citeren
Een expressie tussen aanhalingstekens is een F#-expressie in uw code die zodanig wordt gescheiden dat deze niet wordt gecompileerd als onderdeel van uw programma, maar wordt gecompileerd in een object dat een F#-expressie vertegenwoordigt. U kunt een expressie tussen aanhalingstekens op twee manieren markeren: met typegegevens of zonder typegegevens. Als u typegegevens wilt opnemen, gebruikt u de symbolen en @>
om de expressie tussen aanhalingstekens <@
te scheiden. Als u geen typegegevens nodig hebt, gebruikt u de symbolen <@@
en @@>
. De volgende code toont getypte en niet-getypte aanhalingstekens.
open Microsoft.FSharp.Quotations
// A typed code quotation.
let expr : Expr<int> = <@ 1 + 1 @>
// An untyped code quotation.
let expr2 : Expr = <@@ 1 + 1 @@>
Het doorlopen van een grote expressiestructuur verloopt sneller als u geen typegegevens opneemt. Het resulterende type van een expressie met de getypte symbolen is Expr<'T>
, waarbij de typeparameter het type expressie heeft zoals bepaald door het type deductie-algoritme van de F#-compiler. Wanneer u codecitaten zonder typegegevens gebruikt, is het type van de aanhalingstekenexpressie het niet-generieke type Expr. U kunt de eigenschap Raw in de getypte Expr
klasse aanroepen om het niet-getypte Expr
object op te halen.
Er zijn verschillende statische methoden waarmee u programmatisch F#-expressieobjecten in de Expr
klasse kunt genereren zonder aangeroepen expressies te gebruiken.
Een codeofferte moet een volledige expressie bevatten. Voor een let
binding hebt u bijvoorbeeld zowel de definitie van de afhankelijke naam als een andere expressie nodig die gebruikmaakt van de binding. In uitgebreide syntaxis is dit een expressie die het trefwoord volgt in
. Op het hoogste niveau in een module is dit slechts de volgende expressie in de module, maar in een aanhalingsteken is dit expliciet vereist.
De volgende expressie is daarom niet geldig.
// Not valid:
// <@ let f x = x + 1 @>
Maar de volgende expressies zijn geldig.
// Valid:
<@ let f x = x + 10 in f 20 @>
// Valid:
<@
let f x = x + 10
f 20
@>
Als u F#-aanhalingstekens wilt evalueren, moet u de F#-aanhalings evaluator gebruiken. Het biedt ondersteuning voor het evalueren en uitvoeren van F#-expressieobjecten.
F#-aanhalingstekens behouden ook informatie over typebeperkingen. Kijk een naar het volgende voorbeeld:
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
De beperking die door de inline
functie wordt gegenereerd, wordt bewaard in de codecitaat. Het formulier voor aanroepen van de negate
functie kan nu worden geƫvalueerd.
Expr-type
Een exemplaar van het Expr
type vertegenwoordigt een F#-expressie. Zowel de algemene als de niet-generieke Expr
typen worden beschreven in de F#-bibliotheekdocumentatie. Zie FSharp.Quotations Namespace and Quotations.Expr Class voor meer informatie.
Splicing-operators
Met splicing kunt u letterlijke codecitaten combineren met expressies die u programmatisch of vanuit een andere codecitaat hebt gemaakt. Met de %
operatoren %%
kunt u een F#-expressieobject toevoegen aan een codeaanhalingsteken. U gebruikt de %
operator om een getypt expressieobject in te voegen in een getypt aanhalingsteken. U gebruikt de %%
operator om een niet-getypt expressieobject in te voegen in een niet-getypte aanhalingsteken. Beide operators zijn unaire voorvoegseloperators. Dus als expr
het een niet-getypte expressie van het type Expr
is, is de volgende code geldig.
<@@ 1 + %%expr @@>
En als expr
het een getypte aanhalingsteken van het type Expr<int>
is, is de volgende code geldig.
<@ 1 + %expr @>
Voorbeeld 1
Beschrijving
In het volgende voorbeeld ziet u het gebruik van codecitaten om F#-code in een expressieobject te plaatsen en vervolgens de F#-code af te drukken die de expressie vertegenwoordigt. Er wordt een functie println
gedefinieerd die een recursieve functie print
bevat die een F#-expressieobject (van het type Expr
) in een beschrijvende indeling weergeeft. Er zijn verschillende actieve patronen in de modules FSharp.Quotations.Patterns en FSharp.Quotations.DerivedPatterns die kunnen worden gebruikt om expressieobjecten te analyseren. Dit voorbeeld bevat niet alle mogelijke patronen die kunnen worden weergegeven in een F#-expressie. Een niet-herkend patroon activeert een overeenkomst met het jokertekenpatroon (_
) en wordt weergegeven met behulp van de ToString
methode, waarmee u op basis van het Expr
type het actieve patroon weet dat u aan uw overeenkomstexpressie moet toevoegen.
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 @@>
Uitvoer
fun (x:System.Int32) -> x + 1
a + 1
let f = fun (x:System.Int32) -> x + 10 in f 10
Voorbeeld 2
Beschrijving
U kunt ook de drie actieve patronen in de ExprShape-module gebruiken om expressiestructuren met minder actieve patronen te doorlopen. Deze actieve patronen kunnen handig zijn wanneer u een boom wilt doorlopen, maar u niet alle informatie in de meeste knooppunten nodig hebt. Wanneer u deze patronen gebruikt, komt elke F#-expressie overeen met een van de volgende drie patronen: ShapeVar
als de expressie een variabele is, ShapeLambda
als de expressie een lambda-expressie is of ShapeCombination
als de expressie iets anders is. Als u een expressiestructuur doorkruist met behulp van de actieve patronen zoals in het vorige codevoorbeeld, moet u veel meer patronen gebruiken om alle mogelijke F#-expressietypen te verwerken en is uw code complexer. Zie ExprShape.ShapeVar |ShapeLambda |Active Pattern ShapeCombination.
Het volgende codevoorbeeld kan worden gebruikt als basis voor complexere doorkruisingen. In deze code wordt een expressiestructuur gemaakt voor een expressie die een functieaanroep omvat. add
Het Active SpecificCall-patroon wordt gebruikt om een aanroep in de expressiestructuur te add
detecteren. Met dit actieve patroon worden de argumenten van de aanroep aan de exprList
waarde toegewezen. In dit geval zijn er slechts twee, dus deze worden uitgetrokken en de functie wordt recursief aangeroepen op de argumenten. De resultaten worden ingevoegd in een codeofferte die een aanroep aangeeft mul
met behulp van de operator splice (%%
). De println
functie uit het vorige voorbeeld wordt gebruikt om de resultaten weer te geven.
De code in de andere actieve patroontakken genereert alleen dezelfde expressiestructuur opnieuw, dus de enige wijziging in de resulterende expressie is de wijziging van add
.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
Uitvoer
1 + Module1.add(2,Module1.add(3,4))
1 + Module1.mul(2,Module1.mul(3,4))