Цитирование кода

В этой статье описываются кавычки кода, функция языка, которая позволяет создавать и работать с выражениями кода 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 в типизированном ExprExpr классе.

Существуют различные статические методы, позволяющие создавать объекты выражений 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#, представляющий выражение. Функция определена, содержащая рекурсивную функцию printlnprint , которая отображает объект выражения 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 из предыдущего примера используется для отображения результатов.

Код в других активных ветвях шаблонов просто создает то же дерево выражений, поэтому единственное изменение в результирующем выражении — это изменение с addmul.

Код

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

См. также