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