Print and execute a string

Tag: haskell , template-haskell Author: morefunny Date: 2012-02-13

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.

I'd use CPP. Crude but effective for these kinds of things.
CPP works - until the text you want to quote extends to more than one line...
"I find myself writing a lot of code like [this]" ... why?

Best Answer

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)|]


So what you're saying is, somebody has implemented a Haskell parser as a quasi-quoter, and you can use that? (Gotta love how it says "not yet complete" all over it...) It does seem a pity to not be able to treat GHC's existing Haskell parser as a quasi-quoter in the first place... I wonder why they don't support that?
This is apparently the best that can be done with TH, so I'm going to accept this answer.

Other Answer1

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.

{-# 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))


I had expected TH to support parsing from a string, but as you say, it does not. Most unexpected. I notice you see to use the "real" CPP; you realise that there isn't one on Windows, right?

Other Answer2

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

Other Answer3

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?)


"it's actually impossible" - wrong. "no such function exists [that turns a string into an expression]" - wrong. shang's answer implements both of these. His TH outputs the quoted string exactly as it was input. In fact he implemented the exact thing you asked for, producing precisely that code. It only works for pure, showable expressions, and turns them into an IO (), but that can be easily modified:
It only works for Haskell expressions that the 3rd-party Haskell parser works for, not for any expression which GHC can parse.