Ideally I would like to be able to write things like
-- A macro
$((
zipFields :: String -> [String] -> String
zipFields f fields = $((
intercalate " " ["($f ($field a) ($field b))" | field <- fields]
))
data Point = Point { x :: Double
, y :: Double
}
addPoint :: Point -> Point -> (Double -> Double) -> Point
addPoint a b = Point $((zipFields "(+)" ["x", "y"]))
This is a contrieved example which probably could be writen using Generics
but it shows the type of macro which could be written.
Another simple example is to do calculation at compile time. In
day = 24*60*60
day
might be recalculated everytime it is used whereas in
day = $(( 24*60*60 ))
day is calculate at compile time.
The basic idead is to have splices generating a string, instead of an AST (an Exp
or Type
etc …)
A first pass would extract all slices and execute them (has normal haskell code) and replace them by their result.
The expanded all file would be then be parsed normally
Generating a string to pe parsed vs an AST Exp` has many main advantages.
1- Writing a string is closer to the target code than a expression.
It is easier to write
"(f $a $b)"
than
AppE (VarE $ mkName "f") (AppE (VarE $ mkName a) (VarE $ mkName b)
The string is just Haskell. The Exp
is a new language which need to be learned.
My Exp
is probably wrong, which proves the point.
2- An inconvenient of writing an AST is that it exposes the internal of GHC, which are quite complex and break
Template Haskell codes every time something is added to GHC. This especially true for Type
declaration.
This problem doesn’t exist with string. If something is added to GHC which doesn’t change the syntax,
no change in the macro are needed.
3- Quasi quoting try to offer a similar interface, but only works if all the terms are knows. Also
it has generates “capture/scoping” issue, does a variable name belongs to generating or generated code ?
With strings, name within string will be resolved later on.
4- To be able to typecheck the AST, a splice needs to have all identifier in scope which means that
a file is compile by section. I believe this problem is called (or related to) “stage restriction”.
Same as 3.
The argument for splices generating an AST is it generates something which typecheck.
The counter argument is the generated macro will be typechecked at the next phase, so it is not a
problem in practice. Another counter argument is people who want “pre-typechsecking” can still use TH.
This is orthogonal to pure/impure Template Haskell but could of course be linked to it.