Citazioni di codice (F#)

In questo argomento vengono descritte le quotation di codice, una funzionalità di linguaggio che consente di generare e utilizzare espressioni di codice F# a livello di codice. Questa funzionalità consente di generare una struttura ad albero sintattica astratta che rappresenta il codice F#. La struttura ad albero sintattica astratta può essere quindi attraversata ed elaborata in base alle esigenze dell'applicazione. È ad esempio possibile utilizzare la struttura ad albero per generare codice F# o codice in un altro linguaggio.

Espressioni citate

Un'espressione in quotation è un'espressione F# nel codice delimitata in modo tale da non essere compilata come parte del programma, ma piuttosto in un oggetto che rappresenta un'espressione F#. È possibile contrassegnare un'espressione in quotation in due modi: con o senza informazioni sul tipo. Se si desidera includere le informazioni sul tipo, utilizzare i simboli <@ e @> per delimitare l'espressione in quotation. Se non sono necessarie le informazioni sul tipo, si utilizzano i simboli <@@ e @@>. Nel codice seguente vengono mostrate quotation tipizzate e non tipizzate.

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

Risulta più rapido attraversare una struttura ad albero dell'espressione di grandi dimensioni se non vengono incluse le informazioni sul tipo. Il tipo risultante di un'espressione in quotation con i simboli tipizzati è Expr<'T>, in cui il parametro del tipo presenta il tipo dell'espressione determinato dall'algoritmo di inferenza del tipo del compilatore F#. Se si utilizzano quotation di codice senza le informazioni sul tipo, il tipo dell'espressione in quotation è il tipo Expr non generico. È possibile chiamare la proprietà Raw sulla classe Expr tipizzata per ottenere l'oggetto Expr non tipizzato.

Esistono diversi metodi statici che consentono di generare oggetti di espressione F# a livello di codice nella classe Expr senza utilizzare espressioni in quotation.

Una quotation di codice deve includere un'espressione completa. Per un'associazione let sono ad esempio necessarie la definizione del nome associato e un'espressione aggiuntiva che utilizza l'associazione. In sintassi dettagliata si tratta di un'espressione che segue la parola chiave in. Al livello superiore di un modulo, è l'espressione successiva nel modulo, ma in una quotation è obbligatoria in modo esplicito.

Il codice indicato di seguito non è quindi valido.

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

Sono tuttavia valide le espressioni seguenti:

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

Per utilizzare quotation del codice, è necessario aggiungere una dichiarazione di importazione (tramite la parola chiave open) che apre lo spazio dei nomi Microsoft.FSharp.Quotations.

F# PowerPack fornisce supporto per la valutazione e l'esecuzione di oggetti di espressione F#.

Tipo Expr

Un'istanza del tipo Expr rappresenta un'espressione F#. Entrambi i tipi Expr, generico e non generico, sono illustrati nella documentazione della libreria F#. Per ulteriori informazioni, vedere Spazio dei nomi Microsoft.FSharp.Quotations (F#) e Classe Quotations.Expr (F#).

Operatori di splicing

Lo splicing consente di combinare quotation di codice letterali con le espressioni create a livello di codice o da un'altra quotation di codice. Gli operatori % e %% consentono di aggiungere un oggetto di espressione F# in una quotation di codice. L'operatore % consente di inserire un oggetto di espressione tipizzata in una quotation tipizzata, mentre l'operatore %% consente di inserire un oggetto di espressione non tipizzata in una quotation non tipizzata. Entrambi sono operatori di prefisso unari. Se pertanto expr è un'espressione non tipizzata di tipo Expr, il codice seguente è valido.

<@@ 1 + %%expr @@>

Se inoltre expr è una quotation tipizzata di tipo Expr<int>, il codice seguente è valido.

<@ 1 + %expr @>

Esempio

Descrizione

Nell'esempio seguente viene illustrato l'utilizzo di quotation di codice per inserire codice F# in un oggetto di espressione e visualizzare quindi il codice F# che rappresenta l'espressione. Viene definita una funzione println contenente una funzione print ricorsiva che visualizza un oggetto di espressione F# (di tipo Expr) in un formato intuitivo. Nei moduli Microsoft.FSharp.Quotations.Patterns e Microsoft.FSharp.Quotations.DerivedPatterns sono presenti diversi criteri attivi che possono essere utilizzati per analizzare oggetti di espressione. In questo esempio non sono inclusi tutti i possibili criteri che potrebbero comparire in un'espressione F#. Qualsiasi criterio non riconosciuto attiva una corrispondenza con il carattere jolly (_) e viene visualizzato mediante il metodo ToString che, sul tipo Expr, consente di individuare il criterio attivo da aggiungere all'espressione corrispondente.

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

Descrizione

È inoltre possibile utilizzare i tre criteri attivi nel modulo ExprShape per attraversare strutture ad albero di espressione con un numero inferiore di criteri attivi. Questi criteri attivi possono risultare utili nel caso in cui si desideri attraversare una struttura ad albero ma non siano necessarie tutte le informazioni nella maggior parte dei nodi. Se si utilizzano 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 è di un altro tipo. Se si attraversa una struttura ad albero di espressione mediante criteri attivi come nell'esempio di codice precedente, è necessario utilizzare molti più criteri per gestire tutti i tipi di espressione F# possibili e il codice risulterà più complesso. Per ulteriori informazioni, vedere Criterio attivo ExprShape.ShapeVar|ShapeLambda|ShapeCombination (F#).

L'esempio di codice seguente può essere utilizzato come base per attraversamenti più complessi. In questo codice viene creata una struttura ad albero di espressione per un'espressione che coinvolge una chiamata di funzione, add. Il criterio attivo SpecificCall viene utilizzato per rilevare qualsiasi chiamata a add nella struttura ad albero dell'espressione. Questo criterio attivo assegna gli argomenti della chiamata al valore exprList. In questo caso sono solo due, pertanto vengono estratti e la funzione viene chiamata in modo ricorsivo sugli argomenti. I risultati vengono inseriti in una quotation di codice che rappresenta una chiamata a mul tramite l'operatore di splicing (%%). Per visualizzare i risultati viene utilizzata la funzione println dell'esempio precedente.

Il codice nei rami dell'altro criterio attivo rigenera semplicemente la stessa struttura ad albero dell'espressione, pertanto l'unica modifica nell'espressione risultante è quella rappresentata da add in 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))

Vedere anche

Altre risorse

Riferimenti per il linguaggio F#