Share via


程式碼引號

本文說明程式碼引號,此語言功能可讓您以程式設計方式產生及使用 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。 您可以在具型別的 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# 運算式物件新增至程式碼引號中。 您可使用 % 運算子,將具型別的運算式物件插入具型別的引號中;您可以使用 %% 運算子,將不具型別的運算式物件插入不具型別的引號中。 這兩個運算子都是一元前置運算子。 因此,如果 exprExpr 型別的不具型別運算式,則下列程式碼有效。

<@@ 1 + %%expr @@>

如果 exprExpr<int> 型別的具型別引號,則下列程式碼有效。

<@ 1 + %expr @>

範例 1

描述

下列範例說明使用程式碼引號,將 F# 程式碼放入運算式物件,然後列印代表運算式的 F# 程式碼。 需定義 println 函式,其中包含以好記格式顯示 F# 運算式物件 (型別為 Expr) 的 print 遞迴函式。 FSharp.Quotations.PatternsFSharp.Quotations.DerivedPatterns 模組中有多個現用模式,可用來分析運算式物件。 此範例不包含可能出現在 F# 運算式中的所有可能模式。 任何無法辨識的模式都會觸發與萬用字元模式 (_) 比對,且會在 Expr 型別上使用 ToString 方法來轉譯,讓您知道要新增至比對運算式的現用模式。

代碼

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

代碼

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

另請參閱