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 da sua aplicação. Por exemplo, você pode usar a árvore para gerar código F# ou gerar código em algum outro idioma.
Expressões citadas
Uma expressão entre aspas é uma expressão F# em seu código que é delimitada de tal forma que não é compilada como parte do seu programa, mas em vez disso é compilada em um objeto que representa uma expressão F#. Você pode marcar uma expressão entre aspas de duas maneiras: com informações de tipo ou sem informações de tipo. Se quiser incluir informações de tipo, use os símbolos <@
e @>
delimite 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 cotações digitadas e não tipadas.
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 árvore de expressão grande é mais rápido se você não incluir informações de tipo. O tipo resultante de uma expressão citada 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 cotações de código sem informações de tipo, o tipo da expressão citada é o tipo não genérico Expr. Você pode chamar a propriedade Raw na classe tipada Expr
para obter o objeto não tipado Expr
.
Existem vários métodos estáticos que permitem gerar objetos de expressão F# programaticamente na Expr
classe sem usar expressões entre aspas.
Uma citação de código deve incluir uma expressão completa. Para uma let
ligação, por exemplo, você precisa da definição do nome acoplado e de outra expressão que use a associação. Na sintaxe detalhada, esta é uma expressão que segue a in
palavra-chave. No nível superior de um módulo, esta é apenas a próxima expressão no módulo, mas em uma citação, ela é 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 cotações F#, você deve usar o Avaliador de Cotação F#. Ele fornece suporte para avaliar e executar objetos de expressão F#.
As cotações 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 inline
função é mantida na cotação do código. A negate
forma citada da função pode agora ser avaliada.
Tipo Expr
Uma instância do Expr
tipo representa uma expressão F#. Os tipos genérico e não genérico Expr
estão documentados na documentação da biblioteca F#. Para obter mais informações, consulte FSharp.Quotations Namespace e Quotations.Expr Class.
Operadores de emenda
A emenda permite combinar citações de código literal com expressões que você criou programaticamente ou a partir de outra cotação de código. Os %
operadores e %%
permitem que você adicione um objeto de expressão F# em uma cotação de código. Você usa o %
operador para inserir um objeto de expressão tipado em uma cotação digitada, você usa o %%
operador para inserir um objeto de expressão não tipado em uma cotação não tipada. Ambos os operadores são operadores de prefixo unário. Assim, se expr
é uma expressão não tipada do tipo Expr
, o código a seguir é válido.
<@@ 1 + %%expr @@>
E se expr
for uma cotação digitada do tipo Expr<int>
, o código a seguir é válido.
<@ 1 + %expr @>
Exemplo 1
Description
O exemplo a seguir ilustra o uso de aspas 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 print
recursiva 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 dispara uma correspondência para o padrão curinga (_
) e é renderizado usando o ToString
método, que, no Expr
tipo, permite que você saiba 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
Description
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 atravessar 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 é uma variável, ShapeLambda
se a expressão é uma expressão lambda ou ShapeCombination
se a expressão é qualquer outra coisa. Se você atravessar uma árvore de expressão usando os padrões ativos como no exemplo de código anterior, você terá que usar muitos 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|Padrão ativo ShapeCombination.
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 detetar qualquer chamada na árvore de add
expressão. Esse padrão ativo atribui os argumentos da chamada ao exprList
valor. Neste caso, há apenas dois, então estes são retirados e a função é chamada recursivamente nos argumentos. Os resultados são inseridos em uma cotação de código que representa uma chamada para mul
usando o operador de emenda (%%
). A println
função 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))