Редагувати

Поділитися через


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