Question

I find myself writing a lot of code like

putStr "foo (bar 1) (bar 2) ="
print $ foo (bar 1) (bar 2)

The trouble is, the printed message can get out of sync with the actual executed code. The obvious solution is to auto-generate this code.

One way to do that would be to put all the text in a file, and write a small program that reads the file and generates Haskell source code from it. But another alternative is to use Template Haskell.

Does anybody know how I would go about writing a function that takes a String and generates the above code from it? I'm guessing it should be pretty easy, but TH is not well documented.

Was it helpful?

Solution

You can parse Haskell code using the haskell-src-meta package. Here's a quick example how you could combine this with Template Haskell.

{-# LANGUAGE TemplateHaskell #-}

import Language.Haskell.TH
import Language.Haskell.TH.Quote
import Language.Haskell.Meta

runShow = QuasiQuoter
    { quoteExp  = runShowQQ
    , quotePat  = undefined
    , quoteType = undefined
    , quoteDec  = undefined
    }

runShowQQ :: String -> Q Exp
runShowQQ s = do
    let s'          = s ++ " = "
        Right exp = parseExp s
        printExp  = appE [|print|] (return exp)
    infixApp [|putStr s'|] [|(>>)|] printExp

And you would use it like this

{-# LANGUAGE QuasiQuotes #-}

[runShow|foo (bar 1) (bar 2)|]

OTHER TIPS

Template Haskell does not provide a straightforward means of parsing arbitrary strings, so the simplest solution is probably to use the C preprocessor. However, the built-in one in GHC does not support stringification, so we need to pass extra options to use the "real" one instead.

{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -pgmP cpp #-}

#define PRINT_EXP(x) (putStr #x >> putStr " = " >> print (x))

You can then use it like this:

PRINT_EXP(foo (bar 1) (bar 2))

There is an example of eval-like Haskell code using GHC API here.

Ouch. I thought this would be easy, but as far as I can tell, it's actually impossible.

I was expecting there to be a function that turns a string into an expression, but apparently no such function exists. There isn't even a function to load more source code from disk. So it seems this task is actually impossible! I'm quite surprised at that.

The closest thing I could do is to quote the expression I want to run, and then build a splice that pretty-prints the quoted expression before running it. However, that puts me at the mercy of GHC's expression pretty printer. The label doesn't come out exactly as I typed it. (In particular, it seems to replace operators with fully-qualified names, which is just painful.)

You would have thought a feature like this would be pretty trivial to implement. The fact that it isn't implemented can therefore only be attributed to one of two things:

  1. Nobody actually needs this feature. (Well, except me, obviously.)

  2. It isn't as trivial as it looks. (E.g., maybe figuring out what context to parse the expression in is fiddly somehow?)

You may also use the dump package which was written to handle this exact use-case:

{-# language QuasiQuotes #-}
import Debug.Dump

main = putStrLn [d| foo (bar 1) (bar 2) |]

foo = (+)
bar = (+1)

Which prints: (foo (bar 1) (bar 2)) = 5

It also handles more than one expression separated by commas:

putStrLn [d| foo (bar 1) (bar 2), map bar [1, 2] |]

Which prints: (foo (bar 1) (bar 2)) = 5 (map bar [1, 2]) = [2,3]

Bonus: If you have nix-shell installed (part of the nix package manager) you can even try it out quickly with this "one-liner":

$ nix-shell -p "nix-shell -p "haskellPackages.ghcWithPackages (p: [p.dump])" --run "echo '{-# language QuasiQuotes #-}; import Debug.Dump; foo = (+); bar = (+1); main = putStrLn [d| foo (bar 1) (bar 2), map bar [1, 2] |]' | runhaskell"

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top