Cytaty kodu

W tym artykule opisano cudzysłów kodu— funkcję języka, która umożliwia programowe generowanie wyrażeń kodu języka F# i pracę z nimi. Ta funkcja umożliwia wygenerowanie abstrakcyjnego drzewa składni reprezentującego kod F#. Drzewo składni abstrakcyjnej można następnie przechodzić i przetwarzać zgodnie z potrzebami aplikacji. Na przykład możesz użyć drzewa do wygenerowania kodu F# lub wygenerowania kodu w innym języku.

Wyrażenia cytowane

Wyrażenie cytowane to wyrażenie języka F# w kodzie rozdzielane w taki sposób, że nie jest kompilowane w ramach programu, ale zamiast tego jest kompilowane w obiekcie reprezentującym wyrażenie języka F#. Wyrażenie cytowane można oznaczyć na jeden z dwóch sposobów: z informacjami o typie lub bez informacji o typie. Jeśli chcesz uwzględnić informacje o typie, należy użyć symboli <@ i @> rozdzielić cytowane wyrażenie. Jeśli nie potrzebujesz informacji o typie, użyj symboli <@@ i @@>. Poniższy kod przedstawia wpisane i nietypowe cudzysłowy.

open Microsoft.FSharp.Quotations
// A typed code quotation.
let expr : Expr<int> = <@ 1 + 1 @>
// An untyped code quotation.
let expr2 : Expr = <@@ 1 + 1 @@>

Przechodzenie dużego drzewa wyrażeń jest szybsze, jeśli nie dołączysz informacji o typie. Wynikowy typ wyrażenia cytowanego z typowanymi symbolami to Expr<'T>, gdzie parametr typu ma typ wyrażenia określonego przez algorytm wnioskowania typu kompilatora języka F#. Jeśli używasz cudzysłowów kodu bez informacji o typie, typ wyrażenia cudzysłowu jest wyrażeniem typu innego niż ogólny. Właściwość Raw można wywołać w typowanej Expr klasie, aby uzyskać nietypowy Expr obiekt.

Istnieją różne metody statyczne, które umożliwiają programowe generowanie obiektów wyrażeń języka F# w Expr klasie bez używania wyrażeń cytowanych.

Cudzysłów kodu musi zawierać pełne wyrażenie. let W przypadku powiązania potrzebujesz na przykład zarówno definicji powiązanej nazwy, jak i innego wyrażenia, które używa powiązania. W pełnej składni jest to wyrażenie, które jest zgodne ze in słowem kluczowym. Na najwyższym poziomie w module jest to tylko następne wyrażenie w module, ale w cudzysłowie jest to jawnie wymagane.

W związku z tym następujące wyrażenie jest nieprawidłowe.

// Not valid:
// <@ let f x = x + 1 @>

Ale następujące wyrażenia są prawidłowe.

// Valid:
<@ let f x = x + 10 in f 20 @>
// Valid:
<@
    let f x = x + 10
    f 20
@>

Aby ocenić cudzysłów języka F#, należy użyć ewaluatora cudzysłowów języka F#. Zapewnia obsługę oceniania i wykonywania obiektów wyrażeń języka F#.

Cudzysłów języka F# zachowują również informacje o ograniczeniu typu. Rozważmy następujący przykład:

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

Ograniczenie generowane przez inline funkcję jest zachowywane w cudzysłowie kodu. Formularz negate cytowany funkcji można teraz ocenić.

Typ eksplorowania

Wystąpienie Expr typu reprezentuje wyrażenie języka F#. Zarówno typy ogólne, jak i nieogólne Expr są udokumentowane w dokumentacji biblioteki języka F#. Aby uzyskać więcej informacji, zobacz FSharp.Quotations Namespace and Quotations.Expr Class (Przestrzeń nazw FSharp.Quotations.Quotations.Expr Class).

Operatory splicing

Splicing umożliwia łączenie cudzysłowów kodu literału z wyrażeniami utworzonymi programowo lub z innego cudzysłowu kodu. Operatory % i %% umożliwiają dodanie obiektu wyrażenia języka F# do cudzysłowu kodu. Operator służy % do wstawiania obiektu wyrażeń typowych do wpisanego cudzysłowu. Operator służy %% do wstawiania nietypowego obiektu wyrażenia do nietypowego cudzysłowu. Oba operatory są operatorami jednoargumentowych prefiksów. W związku z tym, jeśli expr jest nietypowym wyrażeniem typu Expr, poniższy kod jest prawidłowy.

<@@ 1 + %%expr @@>

A jeśli expr jest wpisanym cudzysłowem typu Expr<int>, następujący kod jest prawidłowy.

<@ 1 + %expr @>

Przykład 1

opis

Poniższy przykład ilustruje użycie cudzysłowów kodu, aby umieścić kod F# w obiekcie wyrażenia, a następnie wydrukować kod F#, który reprezentuje wyrażenie. Funkcja println jest zdefiniowana, która zawiera funkcję print rekursywną, która wyświetla obiekt wyrażenia F# (typu Expr) w przyjaznym formacie. Istnieje kilka aktywnych wzorców w modułach FSharp.Quotations.Patterns i FSharp.Quotations.DerivedPatterns , których można użyć do analizowania obiektów wyrażeń. Ten przykład nie zawiera wszystkich możliwych wzorców, które mogą występować w wyrażeniu języka F#. Każdy nierozpoznany wzorzec wyzwala dopasowanie do wzorca wieloznacznych (_) i jest renderowany przy użyciu ToString metody , która według Expr typu informuje o aktywnym wzorcu, który ma zostać dodany do wyrażenia dopasowania.

Kod

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

Wyjście

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

Przykład 2

opis

Możesz również użyć trzech aktywnych wzorców w module ExprShape, aby przejść przez drzewa wyrażeń z mniejszą liczbą aktywnych wzorców. Te aktywne wzorce mogą być przydatne, gdy chcesz przejść przez drzewo, ale nie potrzebujesz wszystkich informacji w większości węzłów. Jeśli używasz tych wzorców, dowolne wyrażenie języka F# jest zgodne z jednym z następujących trzech wzorców: ShapeVar jeśli wyrażenie jest zmienną, ShapeLambda jeśli wyrażenie jest wyrażeniem lambda, lub ShapeCombination jeśli wyrażenie jest czymś innym. Jeśli przejdziesz przez drzewo wyrażeń przy użyciu aktywnych wzorców, jak w poprzednim przykładzie kodu, musisz użyć wielu innych wzorców do obsługi wszystkich możliwych typów wyrażeń języka F#, a kod będzie bardziej złożony. Aby uzyskać więcej informacji, zobacz ExprShape.ShapeVar|KształtLambda|Wzorzec aktywny shapeCombination.

Poniższy przykład kodu może służyć jako podstawa bardziej złożonych przechodzenia. W tym kodzie jest tworzone drzewo wyrażeń dla wyrażenia, które obejmuje wywołanie funkcji . add Aktywny wzorzec SpecificCall służy do wykrywania dowolnego wywołania add elementu w drzewie wyrażeń. Ten aktywny wzorzec przypisuje argumenty wywołania exprList do wartości. W tym przypadku istnieją tylko dwa, więc są one ściągane, a funkcja jest wywoływana rekursywnie na argumentach. Wyniki są wstawiane do cudzysłowu kodu reprezentującego wywołanie mul za pomocą operatora splice (%%). Funkcja println z poprzedniego przykładu służy do wyświetlania wyników.

Kod w innych aktywnych gałęziach po prostu ponownie generuje to samo drzewo wyrażeń, więc jedyną zmianą w wyrażeniu wynikowym jest zmiana z add na mul.

Kod

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

Wynik

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

Zobacz też