[Haskell-cafe] Compile-time evaluation
Nicholas Messenger
nmessenger at gmail.com
Fri Nov 2 06:11:53 EDT 2007
{-# OPTIONS_GHC -fglasgow-exts -fno-monomorphism-restriction #-}
-- Many people ask if GHC will evaluate toplevel constants at compile
-- time, you know, since Haskell is pure it'd be great if those
-- computations could be done once and not use up cycles during
-- runtime. Not an entirely bad idea, I think.
--
-- So I set about allowing just that: for arbitrary expressions to be
-- evaluated, and the expanded expression spliced into client code.
--
-- If you had some data in a file just out of convenience, you could say:
-- > yourData = $(compileTimeIO $ parseFile $ readFile "data.txt")
--
-- Or if you had an expensive computation that you want done at compile:
-- > result = $(compileTimeEval $ expensiveComputation)
--
-- I would appreciate comments. I wrote this completely blind with just
-- the TH and Generics haddocks, so if I'm doing something tremendously
-- stupid that can be improved, let me know. :) Especially if you can
-- think of a less awkward way to go from Generics' data to TH
-- expressions than using 'showConstr'...
--
-- I wrote this with 6.6.1, in case there's any incompatibilities. Copy/
-- paste this post into CompileTime.hs, load into ghci, :set -fth, and
-- futz around with the splices.
--
-- -- Nicholas Messenger (nmessenger at gmail.com, omnId on #haskell)
module CompileTime(compileTimeEval, compileTimeIO) where
import Data.Generics
import Language.Haskell.TH
import Control.Monad
import Data.Tree
import Data.Ratio
-- Expands a datum into an expression tree to be spliced into
-- client code.
compileTimeEval :: Data a => a -> ExpQ
compileTimeEval = return . toExp
-- Runs the IO action and splices in the evaluated result datum.
compileTimeIO :: Data a => IO a -> ExpQ
compileTimeIO = liftM toExp . runIO
-- Does the work. :) toTree gets us a tree of constructors, so
-- we mostly just have to fold the tree with AppE, except for
-- TH's bizarre TupE.
toExp :: Data d => d -> Exp
toExp = applyAll . toTree
where
applyAll (Node k args)
| isTuple k = TupE (map applyAll args)
| otherwise = foldl AppE k (map applyAll args)
isTuple (ConE n) = all (==',') (nameBase n)
isTuple _ = False
-- Synonym to shorten the definition of exp below
type Ex a = a -> Exp
-- Turns some datum into a tree of TH expressions representing
-- that datum. The Exp at each node represents the constructor,
-- the subtrees are its arguments.
toTree :: Data d => d -> Tree Exp
toTree x = Node (exp x) (gmapQ toTree x)
where
-- The various ways to turn a (Data d => d) into an
-- Exp representing its constructor.
any = ConE . mkName . deparen . showConstr . toConstr
char = LitE . CharL
int = sigged $ LitE . IntegerL . toInteger
rat = sigged $ LitE . RationalL . toRational
sigged f x = SigE (f x) (ConT . mkName . show $ typeOf x)
-- The above functions combined together for different types.
-- This is what gives the constructor Exp at each Node. There
-- are definitely more types to cover that 'any' gets wrong...
exp = any `extQ` (int::Ex Int) `extQ` (int::Ex Integer)
`extQ` char `extQ` (rat::Ex Float)
`extQ` (rat::Ex Double) `extQ` (rat::Ex Rational)
-- Generics' showConstr puts parens around infix
-- constructors. TH's ConE doesn't like 'em.
deparen s = (if last s == ')' then init else id) .
(if head s == '(' then tail else id) $ s
More information about the Haskell-Cafe
mailing list