I already wrote some Haskell roman numeral code which gives a function
numeralsToInteger :: String -> Integer
. I tweaked its source code a bit (it wasn't a module previously, for example) and can write this:import Roman main = do putStrLn "roman test 1 (this should print 1998 below)" print $ numeralsToInteger "MCMXCVIII"
What I want to do with TH is shorten the syntax and make the roman->literal conversion happen at compile time instead of at runtime.
I'll try to use TH's quasi-quoter syntax, which should allow me to write:
print [ro|MCMXCVIII|]
ro
is the name I've chosen for my quasiquoter, and the [...|...|]
bracket syntax comes from template haskell quasiquoting.What will happen is that everything inside those
[|
brackets|]
will be passed to my code, which can return an arbitrary Haskell expression to be substituted at compile time. Think C-style #define
on steroids.In my case, I'll always be returning an integer literal expression. The value of that literal will be determined by parsing the roman numeral.
I need to define
ro :: Language.Haskell.TH.Quote.QuasiQuoter
, and it needs to live in a different module (so that it can be compiled before attempting to parse anything that uses that quasiquoter)I want to use my quasiquoter to return an expression. They can be used in more contexts than that (for example, when a type is needed). I'll make the other contexts into an
error
, which leaves only the hard one that parses and returns my expression. (after doing a bunch of fiddling Agda, it feels like a relief to be able to say "oh, I'll just make that an error).It turns out that the code is mostly plumbing.
RomanTH.hs
looks like this:module RomanTH where import Roman import Language.Haskell.TH import Language.Haskell.TH.Quote ro :: Language.Haskell.TH.Quote.QuasiQuoter ro = QuasiQuoter parser err err err err = error "Roman numeral quasiquoter cannot be used in this context" parser :: String -> Q Exp parser s = litE (IntegerL (numeralsToInteger s))
which is all types and wiring except the function
parser s = litE (IntegerL (numeralsToInteger s))
which means (right-to-left) convert string to an Integer, embed it in a literal type, and embed that inside an expression.The complete test program looks like this:
import RomanTH main = do putStrLn "roman test 2 (this should print 1998 below)" print [ro|MCMXCVIII|]
and runs like this:
$ ./test2 roman test 2 (this should print 1998 below) 1998
Well, that took all of 36 minutes.
No comments:
Post a Comment