F# quotations visualizer - reloaded!
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:
- Rewritten using active patterns (new F# language feature)
- It is possible to extract quotations from compiled F# assembly (if it contains quotation data)
- Added support for several missing language constructs
Active patterns is a new (experimental) feature in the F# language. You can find some information about this feature in
Don Syme's article . 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
end keywords. This feature is described in the F# manual .
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
Now we have all resources containing F# quotation data, and we can use
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.
-  An upcoming experimental feature: Active Patterns in F# [^] - Don Syme's WebLog on F# and Other Research Projects
-  Optional lightweight syntax [^] - F# manual