Code quotations
This article 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.
Quoted expressions
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 various static methods that allow you to generate F# expression objects programmatically in the Expr
class without using quoted expressions.
A code quotation must include a complete expression. For a let
binding, for example, you need both the definition of the bound name and another 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 evaluate F# quotations, you must use the F# Quotation Evaluator. It provides support for evaluating and executing F# expression objects.
F# quotations also retain type constraint information. Consider the following example:
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
The constraint generated by the inline
function is retained in the code quotation. The negate
function's quoted form can now be evaluated.
Expr type
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 FSharp.Quotations Namespace and Quotations.Expr Class.
Splicing operators
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 @>
Example 1
Description
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 FSharp.Quotations.Patterns and 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.
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 @@>
Output
fun (x:System.Int32) -> x + 1
a + 1
let f = fun (x:System.Int32) -> x + 10 in f 10
Example 2
Description
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.
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
.
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
Output
1 + Module1.add(2,Module1.add(3,4))
1 + Module1.mul(2,Module1.mul(3,4))
See also
Feedback
https://aka.ms/ContentUserFeedback.
Coming soon: Throughout 2024 we will be phasing out GitHub Issues as the feedback mechanism for content and replacing it with a new feedback system. For more information see:Submit and view feedback for