Citações de código

Este artigo descreve citações de código, um recurso de linguagem que permite gerar e trabalhar com expressões de código F# programaticamente. Esse recurso permite gerar uma árvore de sintaxe abstrata que representa o código F#. A árvore de sintaxe abstrata pode então ser percorrida e processada de acordo com as necessidades do seu aplicativo. Por exemplo, você pode usar a árvore para gerar código F# ou gerar código em alguma outra linguagem.

Expressões citadas

Uma expressão entre aspas é uma expressão F# em seu código que é delimitada de forma que não seja compilada como parte de seu programa, mas sim compilada em um objeto que representa uma expressão F#. Você pode marcar uma expressão citada de duas maneiras: com informações de tipo ou sem informações de tipo. Se desejar incluir informações de tipo, use os símbolos <@ e @> para delimitar a expressão entre aspas. Se você não precisar de informações de tipo, use os símbolos <@@ e @@>. O código a seguir mostra as citações digitadas e não digitadas.

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

Percorrer uma grande árvore de expressão é mais rápido se você não incluir informações de tipo. O tipo resultante de uma expressão entre aspas com os símbolos digitados é Expr<'T>, onde o parâmetro type tem o tipo da expressão conforme determinado pelo algoritmo de inferência de tipo do compilador F#. Quando você usa citações de código sem informações de tipo, o tipo da expressão entre aspas é o tipo não genérico Expr. Você pode chamar a propriedade Raw na classe Expr digitada para obter o objeto Expr não digitado.

Há vários métodos estáticos que permitem gerar objetos de expressão F# programaticamente na classe Expr sem usar expressões entre aspas.

Uma citação de código deve incluir uma expressão completa. Para uma associação let, por exemplo, você precisa da definição do nome vinculado e de outra expressão que use a associação. Na sintaxe detalhada, esta é uma expressão que segue a palavra-chave in. No nível superior em um módulo, esta é apenas a próxima expressão no módulo, mas em uma citação, é explicitamente necessária.

Portanto, a expressão a seguir não é válida.

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

Mas as seguintes expressões são válidas.

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

Para avaliar citações em F#, você deve usar o Avaliador de citações em F#. Ele fornece suporte para avaliar e executar objetos de expressão F#.

As citações do F# também retêm informações de restrição de tipo. Considere o seguinte exemplo:

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

A restrição gerada pela função inline é retida na citação do código. A forma entre aspas da função negate agora pode ser avaliada.

Tipo expr

Uma instância do tipo Expr representa uma expressão F#. Os tipos Expr genéricos e não genéricos estão documentados na documentação da biblioteca F#. Para obter mais informações, consulte FSharp.Quotations Namespace e Quotations.Expr Class.

Operadores de splicing

A splicing permite combinar citações de código literais com expressões que você criou programaticamente ou de outra citação de código. Os operadores % e %% permitem que você adicione um objeto de expressão F# em uma citação de código. Você usa o operador % para inserir um objeto de expressão digitado em uma citação digitada; você usa o operador %% para inserir um objeto de expressão sem tipo em uma citação sem tipo. Ambos os operadores são de prefixo unário. Assim, se expr for uma expressão não tipada do tipo Expr, o código a seguir será válido.

<@@ 1 + %%expr @@>

E se expr for uma citação digitada do tipo Expr<int>, o código a seguir é válido.

<@ 1 + %expr @>

Exemplo 1

Descrição

O exemplo a seguir ilustra o uso de citações de código para colocar o código F# em um objeto de expressão e, em seguida, imprimir o código F# que representa a expressão. É definida uma função println que contém uma função recursiva print que exibe um objeto de expressão F# (do tipo Expr) em um formato amigável. Há vários padrões ativos nos módulos FSharp.Quotations.Patterns e FSharp.Quotations.DerivedPatterns que podem ser usados para analisar objetos de expressão. Este exemplo não inclui todos os padrões possíveis que podem aparecer em uma expressão F#. Qualquer padrão não reconhecido aciona uma correspondência com o padrão curinga (_) e é renderizado usando o método ToString, que, no tipo Expr, permite que você conheça o padrão ativo a ser adicionado à sua expressão de correspondência.

Código

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

Saída

fun (x:System.Int32) -> x + 1
a + 1
let f = fun (x:System.Int32) -> x + 10 in f 10

Exemplo 2

Descrição

Você também pode usar os três padrões ativos no módulo ExprShape para percorrer árvores de expressão com menos padrões ativos. Esses padrões ativos podem ser úteis quando você deseja percorrer uma árvore, mas não precisa de todas as informações na maioria dos nós. Quando você usa esses padrões, qualquer expressão F# corresponde a um dos três padrões a seguir: ShapeVar se a expressão for uma variável, ShapeLambda se a expressão for uma expressão lambda ou ShapeCombination se a expressão for qualquer outra coisa. Se você percorrer uma árvore de expressão usando os padrões ativos como no exemplo de código anterior, precisará usar muito mais padrões para lidar com todos os tipos de expressão F# possíveis e seu código será mais complexo. Para obter mais informações, consulte ExprShape.ShapeVar|ShapeLambda|ShapeCombination Padrão ativo.

O exemplo de código a seguir pode ser usado como base para travessias mais complexas. Neste código, uma árvore de expressão é criada para uma expressão que envolve uma chamada de função, add. O padrão ativo SpecificCall é usado para detectar qualquer chamada para add na árvore de expressão. Esse padrão ativo atribui os argumentos da chamada ao exprList valor. Nesse caso, existem apenas dois, então eles são retirados e a função é chamada recursivamente nos argumentos. Os resultados são inseridos em uma citação de código que representa uma chamada para mul usando o operador de splicing (%%). A função println do exemplo anterior é usada para exibir os resultados.

O código nas outras ramificações de padrão ativo apenas regenera a mesma árvore de expressão, portanto, a única alteração na expressão resultante é a alteração de add para mul.

Código

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

Saída

1 + Module1.add(2,Module1.add(3,4))
1 + Module1.mul(2,Module1.mul(3,4))

Confira também