TP

F# quotations visualizer - reloaded!

Quotation Visualizer

Some time ago, I wrote an article about useful utility called F# quotations visualizer. This utility can be used to show visual representation of F# quotations, that can represent (subset of) source code written in F#. There are two ways that you can use to get F# quotations - first is using operators <@@@@ ... @@@@> (this returns quotation of the code written inside the operator), second method is to get quotation of top level definition from compiled F# assembly (you have to explicitly enable this using command line switch --enable-quotation-data while compiling assembly).

Because I added several new features to the original Quotations visualizer, I decided to publish the latest version - here is the list of main improvements:

Active patterns

Active patterns is a new (experimental) feature in the F# language. You can find some information about this feature in Don Syme's article [1]. In simple words, active patterns allows you to write "switch" consisting of functions (patterns) that return 'a option type. When the code is executed, it finds first pattern whose query returned a value (Some(...)) and the body of selected pattern is executed.

This feature makes it very easy to work with F# quotations because all ef[Something] values that are used for querying quotations have the required signature (the Query function that returns 'a option type), so they can be used with the active patterns feature.

In the quotations visualizer, we need to match the expression (type expr) with all possible expression families (ef[Something]) and choose the first one that matches. To see how active patterns work, you can look at the following part of the function that does this. First, without active patterns:

match efForLoop.Query(expr) with
  | Some(nfrom,nto,body) ->
      // Statement: for i=start to end do body; done;
      // .. create tree node ..
  | _ ->
match efWhileLoop.Query(cond,body) with
      // Statement: while condition do body; done;
      // .. create tree node ..
  | _ ->
match efCond.Query(t,(cond,trbody,flbody)) with
      // if (cond) then trbody; else flbody;
      // .. create tree node ..
  | _ ->
      // unknown expression

And with active patterns the source looks like this:

#light
let EFForLoop = efForLoop
let EFWhileLoop = efWhileLoop
let EFCond = efCond

match expr with
  | EFForLoop(nfrom,nto,body) ->
      // Statement: for i=start to end do body; done;
      // .. create tree node ..
  | EFWhileLoop(cond,body) ->
      // Statement: while condition do body; done;
      // .. create tree node ..
  | EFCond(t,(cond,trbody,flbody)) ->
      // if (cond) then trbody; else flbody;
      // .. create tree node ..
  | _ ->
      // unknown expression

This is very simple example and there are many situations where active patterns are even more helpful. You may have also noticed that the code doesn't contain any semicolons. This is the result of another new feature called lightweight syntax (it is turned on by the #light directive) - if you turn it on the whitespace becomes significant and compiler can understand structure of the code using whitespace instead of semicolons, parentheses and begin/end keywords. This feature is described in the F# manual [2].

Extracting quotations from assembly

When you specify --enable-quotation-data switch to the F# compiler, it stores quotation of every top level definition (functions in modules) in the assembly. This quotation can be later retrieved using resolveTopDef function, however to use the function for loading top-level definitions from another assembly, you first have to load quotation data from the assembly. The following snippet shows how to register declarations from assembly.

let asm = Assembly.LoadFile(name) in
  asm.GetManifestResourceNames() |> Array.to_list 
    |> List.filter (fun rn -> 
        rn.StartsWith(pickledDefinitionsResourceNameBase)) 
    |> List.iter (fun rn -> 
        explicitlyRegisterTopDefs asm rn (readToEnd (asm.GetManifestResourceStream(rn))))

This code first loads the assembly (using LoadFile method which accepts assembly file path). Than it gets all managed resources of the assembly and filters only those, which name starts with pickledDefinitionsResourceNameBase (this is a constant declared in Microsoft.FSharp.Quotations.Raw module). Now we have all resources containing F# quotation data, and we can use explicitlyRegisterTopDefs function to load quoted top level definitions. The explicitlyRegisterTopDefs method takes three parameters - assembly, name of the resource and resource data (byte array). When the top level definitions are registered using this method, it is possible to load quotations of functions declared in loaded assembly - and this is exactly what happens when you click on the "Open F# assembly" link in the application. If you are interested in the complete code, look at the attached source code of quotations visualizer application.

Links

Downloads

Published: Sunday, 1 October 2006, 9:39 PM
Author: Tomas Petricek
Typos: Send me a pull request!
Tags: meta-programming, f#