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

Zie ook