Поделиться через


Цитирование кода (F#)

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

Предусмотрены различные статические методы, позволяющие программно создавать объекты выражений языка F# в классе Expr без использования цитируемых выражений.

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

Чтобы использовать цитирование кода, необходимо добавить объявление импорта (с помощью ключевого слова open), открывающего пространство имен Microsoft.FSharp.Quotations.

Средство PowerPack языка F# обеспечивает поддержку для вычисления и выполнения объектов выражений языка F#.

Тип Expr

Экземпляр типа Expr представляет выражение языка F#.Тип Expr (как универсальный, так и не универсальный) описан в документации библиотеки F#.Дополнительные сведения см. в разделах Пространство имен Microsoft.FSharp.Quotations (F#) и Класс Quotations.Expr (F#).

Операторы объединения

Объединение позволяет объединять литеральные цитаты кода с выражениями, созданными программно или взятыми из другой цитаты кода.Операторы % и %% позволяют добавить объект выражения языка F# в цитату кода.Оператор % служит для вставки объекта типизированного выражения в типизированную цитату; оператор %% служит для вставки объекта нетипизированного выражения в нетипизированную цитату.Оба оператора являются унарными префиксными операторами.Таким образом, если expr является нетипизированным выражением типа Expr, приведенный ниже код является допустимым.

<@@ 1 + %%expr @@>

И если expr является типизированной цитатой типа Expr<int>, приведенный ниже код является допустимым.

<@ 1 + %expr @>

Пример

Dd233212.collapse_all(ru-ru,VS.110).gifОписание

В приведенном ниже примере продемонстрировано использование цитат кода для помещения кода языка F# в объект выражения и последующей печати кода языка F#, представляющего это выражение.Определяется функция println, содержащая рекурсивную функцию print, которая отображает объект выражения языка F# (типа Expr) в удобном для восприятия формате.Модули Microsoft.FSharp.Quotations.Patterns и Microsoft.FSharp.Quotations.DerivedPatterns содержат несколько активных шаблонов, которые можно использовать для анализа объектов выражений.Этот пример содержит не все возможные шаблоны, которые могут появляться в выражении языка F#.Любой нераспознанный шаблон вызывает соответствие шаблону с подстановочным знаком (_) и обрабатывается с использованием метода ToString, который для типа Expr позволяет распознать активный шаблон, который требуется добавить в выражение match.

Dd233212.collapse_all(ru-ru,VS.110).gifКод

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

Dd233212.collapse_all(ru-ru,VS.110).gifOutput

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

Пример

Dd233212.collapse_all(ru-ru,VS.110).gifОписание

Для обхода деревьев выражений с меньшим количеством активных шаблонов можно также использовать три активных шаблона из модуля ExprShape.Эти активные шаблоны могут быть удобны, если требуется обойти дерево, но не требуется вся информация из большинства узлов.При использовании этих шаблонов все выражения языка F# соответствуют одному из следующих трех шаблонов: ShapeVar, если выражение является переменной, ShapeLambda, если выражение является лямбда-выражением, или ShapeCombination, если выражение является чем-либо другим.Если обход дерева выражения производится с помощью активных шаблонов, как в предыдущем примере кода, необходимо использовать многие другие шаблоны для обработки всех возможных типов выражений языка F#, и код будет более сложным.Дополнительные сведения см. в разделе Активный шаблон ExprShape.ShapeVar|ShapeLambda|ShapeCombination (F#).

Следующий пример кода можно использовать в качестве основы для более сложных обходов.В этом коде дерево выражения создается для выражения, содержащего вызов функции add.Активный шаблон SpecificCall служит для обнаружения всех вызовов функции add в дереве выражения.Этот активный шаблон присваивает аргументы вызова значению exprList.В данном случае их только два, поэтому они извлекаются и функция вызывается рекурсивно для этих аргументов.Результаты вставляются в цитату кода, которая представляет вызов функции mul, с помощью оператора объединения (%%).Для отображения результатов используется функция println из предыдущего примера.

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

Dd233212.collapse_all(ru-ru,VS.110).gifКод

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

Dd233212.collapse_all(ru-ru,VS.110).gifOutput

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

См. также

Другие ресурсы

Справочник по языку F#