本文說明 程式代碼引號,這是一種語言功能,可讓您以程式設計方式產生和使用 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>,其中 type 參數具有 F# 編譯程式類型推斷演算法所決定的表達式類型。 當您在沒有類型資訊的情況下使用程式代碼引號時,引號表達式的類型是非泛型類型 Expr。 您可以在具型Expr別類別上呼叫 Raw 屬性,以取得不具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 類別。
接合運算子
接合可讓您結合常值程式代碼引號與以程式設計方式建立的運算式,或從另一個程式代碼引號建立的運算式。
%和 %% 運算子可讓您將 F# 表達式物件加入程式代碼引號中。 您可以使用 % 運算子將具類型的表達式物件插入具類型的引號中;您可以使用 %% 運算符將不具類型的表達式物件插入不具類型的引號中。 這兩個運算子都是一元前置運算符。 因此,如果 expr 是類型的不具型 Expr別表達式,則下列程式代碼是有效的。
<@@ 1 + %%expr @@>
如果 expr 是 型別的具型 Expr<int>別引號,則下列程式代碼是有效的。
<@ 1 + %expr @>
範例 1
說明
下列範例說明如何使用程式代碼引號將 F# 程式代碼放入表示式物件,然後列印代表表達式的 F# 程式代碼。
println定義函式,其中包含遞歸函print式,以易記格式顯示 F# 表示式物件(類型Expr為 ) 。
FSharp.Quotations.Patterns 和 FSharp.Quotations.DerivedPatterns 模組中有數個作用中的模式可用來分析表達式物件。 此範例不包含可能出現在 F# 運算式中的所有可能模式。 任何無法辨識的模式會觸發與通配符模式的比對,_而且會使用 ToString 方法來轉譯,在類型上 Expr ,可讓您知道要新增至比對表達式的作用中模式。
Code
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
說明
您也可以使用 ExprShape 模組 中的三個使用中模式來周遊使用中模式較少的表達式樹狀結構。 當您想要周游樹狀結構,但不需要大部分節點中的所有資訊時,這些作用中的模式會很有用。 當您使用這些模式時,任何 F# 表示式都符合下列三種模式之一: ShapeVar 如果表達式是變數、 ShapeLambda 如果表示式是 Lambda 運算式,或是 ShapeCombination 表示式是任何其他模式,則為 。 如果您使用使用中模式來周遊表達式樹狀結構,如先前的程式代碼範例所示,您必須使用更多模式來處理所有可能的 F# 運算式類型,而且您的程式代碼會比較複雜。 如需詳細資訊,請參閱 ExprShape.ShapeVar|ShapeLambda|ShapeCombination 使用中模式。
下列程式代碼範例可作為更複雜的周遊基礎。 這個程式代碼中,會針對涉及函數呼叫的運算式建立表示式樹狀結構。 add
SpecificCall 使用中的模式可用來偵測表達式樹狀結構中的任何呼叫add。 此使用中模式會將呼叫的自變數指派給 exprList 值。 在此案例中,只有兩個,因此會提取這些,並在自變數上以遞歸方式呼叫 函式。 結果會插入程式代碼引號,代表使用接合運算子 (%%) 呼叫 mul 。 上一個範例中的函 println 式是用來顯示結果。
其他使用中模式分支中的程式代碼只會重新產生相同的表達式樹狀結構,因此產生的表達式中唯一變更是從 變更 add 為 mul。
Code
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))