Code Quotations (F#)
This topic describes code quotations, a language feature that enables you to generate and work with F# code expressions programmatically. This feature lets you generate an abstract syntax tree that represents F# code. The abstract syntax tree can then be traversed and processed according to the needs of your application. For example, you can use the tree to generate F# code or generate code in some other language.
A quoted expression is an F# expression in your code that is delimited in such a way that it is not compiled as part of your program, but instead is compiled into an object that represents an F# expression. You can mark a quoted expression in one of two ways: either with type information or without type information. If you want to include type information, you use the symbols<@ and @> to delimit the quoted expression. If you do not need type information, you use the symbols <@@ and @@>. The following code shows typed and untyped quotations.
open Microsoft.FSharp.Quotations // A typed code quotation. let expr : Expr<int> = <@ 1 + 1 @> // An untyped code quotation. let expr2 : Expr = <@@ 1 + 1 @@>
Traversing a large expression tree is faster if you do not include type information. The resulting type of an expression quoted with the typed symbols is Expr<'T>, where the type parameter has the type of the expression as determined by the F# compiler's type inference algorithm. When you use code quotations without type information, the type of the quoted expression is the non-generic type Expr. You can call the Raw property on the typed Expr class to obtain the untyped Expr object.
There are a variety of static methods that allow you to generate F# expression objects programmatically in the Expr class without using quoted expressions.
Note that a code quotation must include a complete expression. For a let binding, for example, you need both the definition of the bound name and an additional expression that uses the binding. In verbose syntax, this is an expression that follows the in keyword. At the top-level in a module, this is just the next expression in the module, but in a quotation, it is explicitly required.
Therefore, the following expression is not valid.
// Not valid: // <@ let f x = x + 1 @>
But the following expressions are valid.
// Valid: <@ let f x = x + 10 in f 20 @> // Valid: <@ let f x = x + 10 f 20 @>
To use code quotations, you must add an import declaration (by using the open keyword) that opens the Microsoft.FSharp.Quotations namespace.
The F# PowerPack provides support for evaluating and executing F# expression objects.
An instance of the Expr type represents an F# expression. Both the generic and the non-generic Expr types are documented in the F# library documentation. For more information, see Microsoft.FSharp.Quotations Namespace (F#) and Quotations.Expr Class (F#).
Splicing enables you to combine literal code quotations with expressions that you have created programmatically or from another code quotation. The % and %% operators enable you to add an F# expression object into a code quotation. You use the % operator to insert a typed expression object into a typed quotation; you use the %% operator to insert an untyped expression object into an untyped quotation. Both operators are unary prefix operators. Thus if expr is an untyped expression of type Expr, the following code is valid.
<@@ 1 + %%expr @@>
And if expr is a typed quotation of type Expr<int>, the following code is valid.
<@ 1 + %expr @>
The following example illustrates the use of code quotations to put F# code into an expression object and then print the F# code that represents the expression. A function println is defined that contains a recursive function print that displays an F# expression object (of type Expr) in a friendly format. There are several active patterns in the Microsoft.FSharp.Quotations.Patterns and Microsoft.FSharp.Quotations.DerivedPatterns modules that can be used to analyze expression objects. This example does not include all the possible patterns that might appear in an F# expression. Any unrecognized pattern triggers a match to the wildcard pattern (_) and is rendered by using the ToString method, which, on the Expr type, lets you know the active pattern to add to your match expression.
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 @@>
You can also use the three active patterns in the ExprShape module to traverse expression trees with fewer active patterns. These active patterns can be useful when you want to traverse a tree but you do not need all the information in most of the nodes. When you use these patterns, any F# expression matches one of the following three patterns: ShapeVar if the expression is a variable, ShapeLambda if the expression is a lambda expression, or ShapeCombination if the expression is anything else. If you traverse an expression tree by using the active patterns as in the previous code example, you have to use many more patterns to handle all possible F# expression types, and your code will be more complex. For more information, see ExprShape.ShapeVar|ShapeLambda|ShapeCombination Active Pattern (F#).
The following code example can be used as a basis for more complex traversals. In this code, an expression tree is created for an expression that involves a function call, add. The SpecificCall active pattern is used to detect any call to add in the expression tree. This active pattern assigns the arguments of the call to the exprList value. In this case, there are only two, so these are pulled out and the function is called recursively on the arguments. The results are inserted into a code quotation that represents a call to mul by using the splice operator (%%). The println function from the previous example is used to display the results.
The code in the other active pattern branches just regenerates the same expression tree, so the only change in the resulting expression is the change from add to 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