本文介绍 代码引号,这是一种语言功能,可用于以编程方式生成和使用 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# 表达式对象添加到代码引号中。 使用运算符将 %
类型化表达式对象插入类型化引号;使用 %%
运算符将非类型化表达式对象插入非类型化引号中。 这两个运算符都是一元前缀运算符。 因此,如果 expr
类型为非类型化表达式 Expr
,则以下代码有效。
<@@ 1 + %%expr @@>
如果是 expr
类型的 Expr<int>
类型引用,则以下代码有效。
<@ 1 + %expr @>
示例 1
DESCRIPTION
下面的示例演示如何使用代码引号将 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
DESCRIPTION
还可以使用 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))