F# —— Quotation 表达式 (一)
发如果你看过我前面的一篇关于自定义Type Provide的博客的话,或许你还记得在当时我使用了Quotation表达式来定义TP 中的成员函数。那么Quotation表达式具体有什么作用,有什么用途呢,下面我们来瞧瞧。
- 由于Quotation表达式在编译时,并不会被编译成IL代码,所以在当前程序运行时,Quotation表达式会被忽略。这是第一种情况。
- 你可以利用F#中的Pattern match分析上面的AST,从而生成新的代码。
- 正如上面的说得一样,Quotation表达式不会被编译成IL代码,但是它会被编译成一个树型的数据结构。此结构类似于语法树(AST),因此对此语法树可以进 行分析,将其转换成可以在其他平台上运行的代码(如SQL,GPU等等,其中转换为SQL就相当于C# 中的LINQ 功能)
在分析其功能之前,我们先看看Quotation 表达式的两种格式:
第一种:其格式是这样的:
let exprInt : Expr<int> = <@ 1 + 1 @>
即用<@ @> 将代码括起来。此时变量exprInt就会是这样的类型——Expr<’T>此 T为括号中的结果值的类型。 注意Quotation 表达式中的代码必须是完整的代码,也就是说必须有返回值,不能有形如 let exprInt : Expr<int> = <@ let x = 1 + 1 @> 这样的。
在第一种格式的情况下,我们需要使用%+ 变量名的形式来调用表达式外部的变量, 如:
let x = <@ 2 @> let exprInt : Expr<int> = <@ (%x : int) + 1 + 1 @>
第二种:格式如下:
let exprNoTy : Expr = <@@ 1 + 1 @@>
用<@@ @@>将代码括起。 相应的,当引用外部变量时需要使用%%+ 变量名来实现:
let y = <@@ 3 @@> let exprNoTy : Expr = <@@ (%%y:int) + 1 + 1 @@>
下面我们来看看第二个功能的示例(第一个功能直接不做处理即可。。):
namespace QuotationExp open Microsoft.FSharp.Quotations open Microsoft.FSharp.Quotations.Patterns open Microsoft.FSharp.Quotations.DerivedPatterns module Quotation = let rec analyzeExpr (expr : Microsoft.FSharp.Quotations.Expr) = match expr with //如果是<@@ 1 @@>则 执行此代码 | Int32(value) -> printfn " integer : %d" value //如果是<@@ “1“ @@>则 执行此代码 | String(value) -> printfn "string : %s" value //如果是<@@ 1.0 @@>则 执行此代码 | Double(value) -> printfn "double number : %f" value //如果是<@@ x @@>则 执行此代码,即 如果是变量就执行此行,如当要分析Lambda 表达式中的参数时 :如分析fun x -> x + 1 中的x | Var(var) -> printfn "variable : %A" var //如果是<@@ fun x -> x + 1 @@>则 执行此代码 | Lambda(var,body) -> printfn "lambda expression: %A" body //当出现的是你指定的函数的时候执行此行, 注意 特定函数必须放在 Call之前 |SpecificCall <@ (+) @> (inst,methodInfo,[lArg;rArg]) -> printfn "calculate the sum of tow arguments : %A, %A" lArg rArg analyzeExpr lArg analyzeExpr rArg //如果表达式是一个函数的时候 且不是上面特定的函数时 执行此行 | Call(callInst,methodInfo,args) -> match callInst with | Some(inst) -> printfn "%A invoke the method" inst | None -> printfn "this is a static method" printfn "the method is : %s" methodInfo.Name printf "the args of the method: " args |> List.iter(fun x -> analyzeExpr x) | _ -> printfn "Something else" let expr = <@@ (2 * 3) * 1 @@> analyzeExpr(expr)
注意:通过分析AST你可以生成自己想要的新代码,也就是在每个 -> 后面的代码,而且每条情况 -> 后面的代码必须是返回相同类型的值。 代码中的Int32 ,String, Double是系统定义的匹配项,因此可以直接进行匹配。