Цитирование кода
В этой статье описываются кавычки кода, функция языка, которая позволяет создавать и работать с выражениями кода F#программными средствами. Эта функция позволяет создать абстрактное дерево синтаксиса, представляющее код F#. Затем можно пройти и обработать абстрактное дерево синтаксиса в соответствии с потребностями приложения. Например, дерево можно использовать для создания кода F# или создания кода на другом языке.
Кавычки выражений
Кавычекное выражение — это выражение F# в коде, разделяемое таким образом, чтобы оно не компилировалось как часть программы, а компилируется в объект, представляющий выражение F#. Вы можете пометить кавычки одним из двух способов: с информацией о типе или без сведений о типе. Если вы хотите включить сведения о типе, используйте символы и @>
разделители кавычки<@
. Если вам не нужны сведения о типе, используйте символы <@@
и @@>
. В следующем коде показаны типизированные и нетипизированные кавычки.
open Microsoft.FSharp.Quotations
// A typed code quotation.
let expr : Expr<int> = <@ 1 + 1 @>
// An untyped code quotation.
let expr2 : Expr = <@@ 1 + 1 @@>
Обход большого дерева выражений быстрее, если не включать сведения о типе. Результирующий тип выражения, кавычекированного с типизированными символами, — Expr<'T>
где параметр типа имеет тип выражения, определяемый алгоритмом вывода типа F#. При использовании кавычек кода без сведений о типе тип кавычек является не универсальным типом Expr. Чтобы получить нетипизированный объект, можно вызвать свойство Raw в типизированном Expr
Expr
классе.
Существуют различные статические методы, позволяющие создавать объекты выражений Expr
F# программным способом в классе без использования кавычек.
Кавычки кода должны содержать полное выражение. let
Например, для привязки требуется определение привязанного имени и другого выражения, которое использует привязку. В подробном синтаксисе это выражение, которое следует in
ключевое слово. На верхнем уровне модуля это просто следующее выражение в модуле, но в кавычках это явно необходимо.
Поэтому недопустимое следующее выражение.
// Not valid:
// <@ let f x = x + 1 @>
Но допустимы следующие выражения.
// Valid:
<@ let f x = x + 10 in f 20 @>
// Valid:
<@
let f x = x + 10
f 20
@>
Чтобы оценить кавычки F#, необходимо использовать средство оценки цитат F#. Она обеспечивает поддержку оценки и выполнения объектов выражений F#.
Кавычки F# также сохраняют сведения о ограничении типа. Рассмотрим следующий пример:
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
Ограничение, inline
созданное функцией, сохраняется в кавычках кода. Теперь negate
можно оценить кавычки функции.
Тип Expr
Экземпляр Expr
типа представляет выражение F#. Универсальные и не универсальные Expr
типы описаны в документации по библиотеке F#. Дополнительные сведения см. в разделе пространства имен FSharp.Quotations и класса Quotations.Expr.
Операторы splicing
Splicing позволяет объединять кавычки литерального кода с выражениями, созданными программным способом или из другой кавычки кода. %%
Операторы %
позволяют добавить объект выражения F# в кавычки кода. Оператор используется для вставки объекта типизированного выражения в типизированные кавычки. %%
Оператор используется %
для вставки объекта нетипизированного выражения в нетипизированные кавычки. Оба оператора являются унарными операторами префикса. Таким образом, если expr
является нетипизированным выражением типа Expr
, допустимый следующий код.
<@@ 1 + %%expr @@>
И если expr
типизированный кавычки типа Expr<int>
, то допустимый следующий код.
<@ 1 + %expr @>
Пример 1
Description
В следующем примере показано использование кавычек кода, чтобы поместить код F# в объект выражения, а затем распечатать код F#, представляющий выражение. Функция определена, содержащая рекурсивную функцию println
print
, которая отображает объект выражения F# (типа Expr
) в понятном формате. Существует несколько активных шаблонов в модулях FSharp.Quotations.Patterns и FSharp.Quotations.DerivedPatterns , которые можно использовать для анализа объектов выражений. В этом примере не содержатся все возможные шаблоны, которые могут отображаться в выражении F#. Любой нераспознанный шаблон активирует совпадение с диким карта шаблоном (_
) и отображается с помощью ToString
метода, который Expr
в типе позволяет знать активный шаблон для добавления в выражение соответствия.
Код
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 @@>
Выходные данные
fun (x:System.Int32) -> x + 1
a + 1
let f = fun (x:System.Int32) -> x + 10 in f 10
Пример 2
Description
Вы также можете использовать три активных шаблона в модуле ExprShape для обхода деревьев выражений с меньшим количеством активных шаблонов. Эти активные шаблоны могут быть полезны, если вы хотите пройти по дереву, но вам не нужны все сведения в большинстве узлов. При использовании этих шаблонов любое выражение F# соответствует одному из следующих трех шаблонов: ShapeVar
если выражение является переменной, ShapeLambda
если выражение является лямбда-выражением или ShapeCombination
если выражение является любым другим. Если вы проходите по дереву выражений с помощью активных шаблонов, как в предыдущем примере кода, необходимо использовать множество шаблонов для обработки всех возможных типов выражений F#, и код будет более сложным. Дополнительные сведения см. в разделе ExprShape.ShapeVar|ShapeLambda|Активный шаблон ShapeCombination.
Следующий пример кода можно использовать в качестве основы для более сложных обходов. В этом коде для выражения, включающего вызов add
функции, создается дерево выражений. Активный шаблон SpecificCall используется для обнаружения любого вызова add
в дереве выражений. Этот активный шаблон назначает аргументы вызова значению exprList
. В этом случае есть только два, поэтому они извлекаются и функция вызывается рекурсивно в аргументах. Результаты вставляются в кавычки кода, представляющей вызов mul
с помощью оператора splice (%%
). Функция println
из предыдущего примера используется для отображения результатов.
Код в других активных ветвях шаблонов просто создает то же дерево выражений, поэтому единственное изменение в результирующем выражении — это изменение с add
mul
.
Код
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
Выходные данные
1 + Module1.add(2,Module1.add(3,4))
1 + Module1.mul(2,Module1.mul(3,4))