Virgolette di codice

Questo articolo descrive le virgolette di codice, una funzionalità del linguaggio che consente di generare e usare espressioni di codice F# a livello di codice. Questa funzionalità consente di generare un albero della sintassi astratta che rappresenta il codice F#. L'albero della sintassi astratta può quindi essere attraversato ed elaborato in base alle esigenze dell'applicazione. Ad esempio, è possibile usare l'albero per generare codice F# o generare codice in un altro linguaggio.

Espressioni tra virgolette

Un'espressione tra virgolette è un'espressione F# nel codice delimitata in modo da non essere compilata come parte del programma, ma viene invece compilata in un oggetto che rappresenta un'espressione F#. È possibile contrassegnare un'espressione tra virgolette in uno dei due modi seguenti: con informazioni sul tipo o senza informazioni sul tipo. Se si desidera includere informazioni sul tipo, utilizzare i <@ simboli e @> per delimitare l'espressione tra virgolette. Se non sono necessarie informazioni sul tipo, usare i <@@ simboli e @@>. Il codice seguente mostra le virgolette tipate e non tipate.

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

L'attraversamento di un albero delle espressioni di grandi dimensioni è più rapido se non si includono informazioni sul tipo. Il tipo risultante di un'espressione racchiusa tra virgolette con i simboli tipizzato è Expr<'T>, dove il parametro di tipo ha il tipo dell'espressione come determinato dall'algoritmo di inferenza del tipo del compilatore F#. Quando si usano virgolette di codice senza informazioni sul tipo, il tipo dell'espressione tra virgolette è il tipo non generico Expr. È possibile chiamare la proprietà Raw nella classe tipizzata Expr per ottenere l'oggetto non tipizzato Expr .

Esistono vari metodi statici che consentono di generare oggetti espressione F# a livello di codice nella Expr classe senza usare espressioni tra virgolette.

Una virgoletta di codice deve includere un'espressione completa. Per un'associazione let , ad esempio, è necessaria sia la definizione del nome associato che un'altra espressione che usa l'associazione. Nella sintassi dettagliata si tratta di un'espressione che segue la in parola chiave . Al livello superiore di un modulo, questa è solo l'espressione successiva nel modulo, ma in una citazione è esplicitamente obbligatoria.

Pertanto, l'espressione seguente non è valida.

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

Ma le espressioni seguenti sono valide.

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

Per valutare le virgolette F#, è necessario usare l'analizzatore di offerte F#. Fornisce supporto per la valutazione e l'esecuzione di oggetti espressione F#.

Le virgolette F# mantengono anche le informazioni sui vincoli di tipo. Si consideri l'esempio seguente:

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

Il vincolo generato dalla inline funzione viene conservato tra virgolette di codice. Il negate formato tra virgolette della funzione può ora essere valutato.

Tipo expr

Un'istanza del Expr tipo rappresenta un'espressione F#. Sia i tipi generici che i tipi non generici Expr sono documentati nella documentazione della libreria F#. Per altre informazioni, vedere Spazio dei nomi FSharp.Quotations e classe Quotations.Expr.

Operatori di splicing

L'splicing consente di combinare virgolette di codice letterale con espressioni create a livello di codice o da un'altra virgoletta di codice. Gli % operatori e %% consentono di aggiungere un oggetto espressione F# in un'offerta di codice. Utilizzare l'operatore % per inserire un oggetto espressione tipizzata in una virgoletta tipizzata. Utilizzare l'operatore %% per inserire un oggetto espressione non tipizzata in un'offerta non tipizzata. Entrambi gli operatori sono operatori di prefisso unario. Pertanto, se expr è un'espressione non tipizzata di tipo Expr, il codice seguente è valido.

<@@ 1 + %%expr @@>

E se expr è una citazione tipizzata di tipo Expr<int>, il codice seguente è valido.

<@ 1 + %expr @>

Esempio 1

Descrizione

Nell'esempio seguente viene illustrato l'uso delle virgolette di codice per inserire il codice F# in un oggetto espressione e quindi stampare il codice F# che rappresenta l'espressione. Viene definita una funzione println che contiene una funzione print ricorsiva che visualizza un oggetto espressione F# (di tipo Expr) in un formato descrittivo. Esistono diversi modelli attivi nei moduli FSharp.Quotations.Patterns e FSharp.Quotations.DerivedPatterns che possono essere usati per analizzare gli oggetti espressione. Questo esempio non include tutti i possibili modelli che potrebbero essere visualizzati in un'espressione F#. Qualsiasi criterio non riconosciuto attiva una corrispondenza con il modello con caratteri jolly (_) e viene eseguito il rendering usando il ToString metodo , che, nel Expr tipo, consente di conoscere il modello attivo da aggiungere all'espressione di corrispondenza.

Codice

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

Esempio 2

Descrizione

È anche possibile usare i tre modelli attivi nel modulo ExprShape per attraversare gli alberi delle espressioni con un minor numero di modelli attivi. Questi modelli attivi possono essere utili quando si vuole attraversare un albero, ma non sono necessarie tutte le informazioni nella maggior parte dei nodi. Quando si usano questi criteri, qualsiasi espressione F# corrisponde a uno dei tre criteri seguenti: ShapeVar se l'espressione è una variabile, ShapeLambda se l'espressione è un'espressione lambda o ShapeCombination se l'espressione è diversa. Se si attraversa un albero delle espressioni usando i modelli attivi come nell'esempio di codice precedente, è necessario usare molti altri modelli per gestire tutti i possibili tipi di espressione F# e il codice sarà più complesso. Per altre informazioni, vedere ExprShape.ShapeVar|ShapeLambda |Pattern attivo ShapeCombination.

L'esempio di codice seguente può essere usato come base per attraversamenti più complessi. In questo codice viene creato un albero delle espressioni per un'espressione che include una chiamata di funzione, add. Il modello attivo SpecificCall viene usato per rilevare qualsiasi chiamata a add nell'albero delle espressioni. Questo modello attivo assegna gli argomenti della chiamata al exprList valore . In questo caso, sono presenti solo due, quindi vengono estratti e la funzione viene chiamata in modo ricorsivo sugli argomenti. I risultati vengono inseriti in una virgoletta di codice che rappresenta una chiamata a mul usando l'operatore splice (%%). La println funzione dell'esempio precedente viene usata per visualizzare i risultati.

Il codice negli altri rami del criterio attivo rigenera semplicemente lo stesso albero delle espressioni, quindi l'unica modifica nell'espressione risultante è la modifica da add a mul.

Codice

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

Vedi anche