TP

F# - Simple quotations transformation

This article describes very simple code that I wrote while learning how to work with the F# quotations library. Using the F# quotations you can get a tree representation of a quoted expression. This allows you to write code that takes code written in F# as data and performs some code analysis or compiles/translates that code to a different language. This is very similar to the new C# 3.0 expression trees where you can get an expression tree from lambda expressions and translate this tree for example to SQL (using DLINQ). However expression trees in C# 3.0 are very limited when compared with F# quotations, so that's one of the many reasons why F# is an interesting language.

Introduction to F# quotations

If you want to see how F# represents some expressions you can try it in the FSI (F# interactive). First you need to open three modules that contain functions for working with quotations:

open Microsoft.FSharp.Quotations;;
open Microsoft.FSharp.Quotations.Raw;;
open Microsoft.FSharp.Quotations.Typed;;

Now you can see the internal representation of quotations by writing a quoted expression (In VS.Net you can select code and hit Alt+Enter to send the selected code to F# interactive).

<@@ 1 + 2 @@>

val it : int expr
  = <@@ 
      Microsoft.FSharp.MLLib.Pervasives.op_Addition (Int32 1) (Int32 2)
     @@> 

One more example with two operators:

<@@ 1 + 10/5 @@>

val it : int expr
  = <@@ 
      Microsoft.FSharp.MLLib.Pervasives.op_Addition (Int32 1)
        (Microsoft.FSharp.MLLib.Pervasives.op_Division (Int32 10) (Int32 5))
     @@>

It is not difficult to understand that the addition in the first example is represented as a function call (function application in the terminology of functional languages) where the function is the op_Addition operator and the parameters are two integers. This is actually a little simplification, because in functional programming you should look at this code as two function applications. The first application binds the first parameter of op_Addition to 1 and the result is again a function. The second application uses the function returned by the first application and passes 2 as a parameter. You can look at F# quotations using both approaches and in the rest of the article I will use the first one.

To demonstrate how to work with F# quotations I decided to write simple transformations of limited mathematical expressions from F# quotations to my data type. The discriminated union that I'm using as target of the transformation is similar to the union in the F# source file VS.Net template (as you can see it supports only four most basic mathematical operations and all values are stored as integers):

type simple_expr =
  | Int of int
  | Add of simple_expr * simple_expr
  | Sub of simple_expr * simple_expr
  | Mul of simple_expr * simple_expr
  | Div of simple_expr * simple_expr;;

Printing and evaluation of this structure is simple, so I won't describe these here. If you want to look at these functions see the attached source code. (function for evaluation is part of VS.Net template too).

Working with quotations

Finally, let's do the interesting part :-). As I mentioned you can get quotations using <@@ ... @@>. When you want to perform analysis of quotations, you need to get the underlying raw representation using the <@@@@ ... @@@@> operators. For decomposing the quoted expression we can use the Query functions on the expression families defined in the Raw module. This may sound a bit confusing, but in simple words - the Raw module contains several values that define language constructs (like constants, variables, function application and others) and you can use these values for matching and decomposing quoted expressions. Following code should clarify this a bit:

let what_is x =
  match Raw.efInt32.Query x with  
    | Some (_) -> "number";
    | _ -> 
  match Raw.efApps.Query x with  
    | Some (_) -> "application";
    | _ -> 
      "something else...";
	    
// Prints "number"	    
print_string (what_is <@@@@ 1 @@@@>);

// Prints "application"	    
print_string (what_is <@@@@ 1 + 2 @@@@>);

Now we can start writing a function that converts a mathematical expression quotation to a simple_expr structure. It will be a recursive function with similar structure as the previous one. The first match for the conversion of numbers will be simple. In the second match we'll have to do two things. First we'll need to determine what function is applied (whether it is addition, subtraction, etc..) and then we'll have to loop through parameters passed to this function and call the function recursively for each parameter.

let rec parse x =
  match Raw.efInt32.Query x with  
    // x contains the number so we can simply return Int(x)
    | Some (x) -> Int(x); 
    | _ -> 
  match Raw.efApps.Query x with  
    | Some (op,args) -> 
    
      // op contains quoted reference to function (operator)
      // so we need to extract name of applied function from it
      let opname = (
        match Raw.efAnyTopDefn.Query op with  
          // operators are top-level definitons so we extract name
          // from 'a' that contains info about the definiton
          | Some (a,_) -> let t1,t2 = a.topDefPath in t2
          | _ -> failwith ("Function is not a top level definition.");
        ) in
      
      // Recursively translate arguments to simple_expr 
      // and convert returned list to array
      let av = List.to_array(List.map (fun arg -> parse arg) args) in
      
      // Finally, match the operation name with basic 
      // operator names and return according simple_expr value
      (match opname with
        | "op_Addition"    -> Add(av.(0),av.(1))
        | "op_Subtraction" -> Sub(av.(0),av.(1))
        | "op_Multiply"    -> Mul(av.(0),av.(1))
        | "op_Division"    -> Div(av.(0),av.(1))
        | _ ->
          // some other operation - error
          failwith "Not supported operation");
    | _ ->
      // something else than efApps and efInt32 - error
      failwith "Not supported construction in expression.";;

As I mentioned, the more interesting part of the code is when the quotation matches with efApps which in our case means that the expression is a mathematical operation. From the functional point of view efApps means series of function applications. The result of the Query function of efApps is a tuple containing expressions with information about the applied function (operator in our case) and parameters of this application.

For extracting the operator name we match the first value with efAnyTopDef which returns information about top-level definitions. The returned structure contains the assembly name as well as the function name that we need. In the second step we recursively call the parse function to all arguments of the operator. Once we know the function name and we have the arguments translated to simple_expr, we can return the according simple_expr value.

Published: Sunday, 28 May 2006, 3:58 PM
Author: Tomas Petricek
Typos: Send me a pull request!
Tags: meta-programming, f#