There are many reasons programmers may want to use metaprogramming facilities, but one of the most common is for greater efficiency. The premise is simple: if runtime data becomes available at different times, a metaprogramming environment may allow the programmer to generate specialized versions of code based upon certain data which is known sooner, leading to more efficient computations.
A running example in a great deal of literature is the power function. Given pow a b = b ^ a, for certain values of a a much more specialized function can be created, e.g.
pow 1 b = b
pow 2 b = b*b
pow 4 b = let sq = b*b in sq*sq
pow n b = b ^ n
but writing out all possible cases like this is far from ideal. It's tedious and error-prone, and it still won't result in the hoped-for performance gains. Another problem is that it's generally not possible for the programmer to enumerate every possible case. A better solution is desired.
Template Haskell is generally disparaged as a possible solution because it's a compile-time facility. A meta-programming approach to the pow function would be this Template Haskell expression:
mkPow 0 = [| const 1 |]
mkPow n = [| \x -> x * $(unMeta $ mkPow (n-1)) x |]
while this works, Template Haskell splices may only be evaluated at compile-time. For a wide range of interesting metaprogramming problems, template haskell simply isn't available. However, by using the GHC API, we can make use of Template Haskell during runtime. By using a package like plugins or hint, the code is remarkably simple.
I've placed a proof-of-concept implementation on github. meta-expressions are created in the Meta newtype, which wraps Template Haskell's ExpQ type. The Meta monad has an extra phantom type parameter to assist with type safety and inference. Here's a simple example of Meta in use, with mkPow essentially as defined above:
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wall #-}
module Data.Meta.Example where
import Data.Meta.Meta
import Data.Meta.ExampleExpr
main :: IO ()
main = do
n1 <- readLn
let expr1 = mkPow n1 :: Meta (Int -> Int)
n2 <- readLn
let expr2 = mkPow n2 :: Meta (Int -> Int)
expr3 = compose expr1 expr2
result <- metaCompile expr3
case result of
Left err -> putStrLn "failed" >> putStrLn err
Right fn -> do
j <- readLn
print $ fn j
k <- readLn
print $ fn k
Incidentally, this approach seems remarkably similar to the ideas in Geoffrey Mainland's presentation at ICFP 2012. The big differences are that he defines his own quasi-quoters instead of using Template Haskell, and he also uses a compiler other than GHC. Both of these are necessary if you want to metaprogram in a language other than Haskell, naturally.
do u know define-compiler-macro?
ReplyDeleteNot well enough to comment on the performance characteristics, no. I'm not a lisp expert, and certainly not qualified to comment on any specific implementations. In principle this seems like exactly the sort of thing lisp is good at. I just wanted to demonstrate that it isn't particularly difficult in Haskell either, and doesn't need any special support beyond what ghc already provides.
Delete