A look at QuasiQuotation

Posted on May 25, 2012 by Mike Ledger

A week or so ago I finished QuasiText, a small Text interpolation library to generate at compile-time expressions with interpolated variables and/or Haskell code.

Building a QuasiQuoter

Trying to figure out how to build a QuasiQuoter wasn’t hard at all. Partly because of the excellent documentation, and partly because of my hard work annoying others in #haskell.

At the core of a TemplateHaskell splice, as far as expressions go, is the Q Exp. A Q Exp can be evaluated at compile time using the splice syntax $(TemplateHaskell functions evaluating to a Q Exp go here). A QuasiQuoter is simply a function that takes a string and returns a Q Exp, Q Pat, Q Type or Q [Dec].

For example, to define a nice and silly QuasiQuoter that does nothing but return the string “yeah!!!”, we can do:

You will have noticed the use of the big scary [| … |]s. All these are doing in silly’s case is “lifting” the String “yeah!!!” into a Q Exp. For information about lifting, you can look at the docs for the Lift typeclass. (note that [| … |] is syntactic sugar for lift …)

Using the functions given by Language.Haskell.TH for creating Q Exps from fairy dust, if we were at some point feeling unsatisfied by string literals in Haskell, we could define:

Another way of writing it, using lift instead, could be:

For trivial examples like this, it doesn’t matter what “style” you choose.

Lets forget about QuasiQuoters for a moment, and remember that no Haskell tutorial is complete without a fibonacci sequence function. Lets make a compile-time function to return the code to get the nth fibonacci number.

If we fire up GHCi and type in $(fibQ 10), we’ll get the 10th fibonacci number. All we’re really doing is lifting the expression fibs !! n to be a Q Exp, then evaluating this Q Exp with $(…) .

Now, back to the thing. At the time I wrote QuasiText, I wanted some nice Text (from Data.Text, the string type all the cool people use) interpolation without any weird unpacking and packing done at runtime (Text recently got “real” Text literals, which makes me feel very awkward about having to pack Strings -> Text).

Lets say that we want the DSL to look like: “[intp|text goes here $funs $go $here |]”, to implement this, we should first make a parser to translate strings into our abstract format.

Building the parser: QFormat.hs

From this, we can parse Text into [Chunk]:

But, what we obviously want is for all the Left xs to be turned into Text, and then all the Right ys to be turned into their respective definitions in the given source file, and then for the result to be folded together with T.append (although in actual code I tend to use Data.Monoid’s (<>)).

Putting it all together

The above sounds like just the job for a QuasiQuoter! Lets give it a try. For convenience I will also define an instance for Lift Text, allowing us to lift Text values.

And now the moments of truth: does it work? Let us consult GHCi for the answer. I set -ddump-splices to show the intermediate splices that GHC generates. Also note that it needs OverloadedStrings to work for it to evaluate to Text at all.

So there we have it! I hope you found this little tutorial useful and/or enlightening. Making it certainly was a learning process in and of itself. My actual library, which is frighteningly similar to the code we’ve hacked up here, although you can embed arbitrary Haskell expressions (leveraging the power of haskell-src-meta) into QuasiText interpolated strings.

But wait!

Antoine Latter in the comments section pointed out: “You’ll probably do less allocation at run-time if you use the ‘Data.Text.concat’ function.” I concur with this statement.

So, lets refactor:

This is decidedly simpler than the example above, but for the sake of illustrating how to use TemplateHaskell, I’ll keep the former examples.


blog comments powered by Disqus