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