7

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.

MathematicalOrchid
  • 61,854
  • 19
  • 123
  • 220

5 Answers5

10

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)|]
shang
  • 24,642
  • 3
  • 58
  • 86
  • 3
    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? – MathematicalOrchid Feb 29 '12 at 13:21
  • This is apparently the best that can be done with TH, so I'm going to accept this answer. – MathematicalOrchid Mar 02 '12 at 15:42
4

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))
hammar
  • 138,522
  • 17
  • 304
  • 385
  • 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? – MathematicalOrchid Feb 29 '12 at 13:22
1

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

ulidtko
  • 14,740
  • 10
  • 56
  • 88
0

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"

Wizek
  • 4,854
  • 2
  • 25
  • 52
0

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

MathematicalOrchid
  • 61,854
  • 19
  • 123
  • 220
  • 1
    "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: http://hpaste.org/64551 – Dan Burton Feb 29 '12 at 18:32
  • 1
    It only works for Haskell expressions that the 3rd-party Haskell parser works for, not for any expression which GHC can parse. – MathematicalOrchid Mar 02 '12 at 15:09