4

In a very simplified sense, I have something like the following:

type Runtime a = {- More or less a StateT on top of an Either monad -}

-- The list of strings in Fn is a bunch of parameter names, the values of
-- which are pushed into the state of the runtime before executing the actual
-- function expr
data Expr = Num Int
          | Str T.Text
          | Fn [T.Text] (Runtime Expr)
          | {- Bunch of other constructors -}

eval :: Expr -> Runtime Expr
parseExp :: Parser Expr

Now, I've never used Template Haskell for anything before I decided it'd be convenient to have a quasi quoter for my toy language, so I admit I might be missing something obvious.

But in any case, I started fiddling around a bit with it, followed some tutorials etc. and basically found that everything except how to deal with the Fn constructor was easy.

During my information foragings on the web, I found two general ways people wrote the expression quoter:

  • Making their Expr data type an instance of TH:s Lift and simply [| quote |] the expression that the parse resulted in
  • Deriving Data and Typeable for their equivalent of Expr, and then apply dataToExpQ on the same parser result

In both cases, I ran into complications with the Runtime Expr. For the first case, the problem was that I couldn't figure out how to implement:

instance Lift Expr where
  lift (Fn ps e) = [| Fn ps ...? |]

(I did manage to implement the instance for Data.Text on my own though).

I suppose the real issue is that I simply don't know TH well enough yet, but no amount of tutorials or exmaples so far has helped me get anywhere with this.

In the second case, the issue was that for Expr to be an instance of Data, there also needs to be an

instance Data (StateT (...) (Either ...) Expr) where
  -- Something

My question then, is whether there's an easy way to do this? Or perhaps I should rethink how the functions of my toy language work?

If the latter, any recommendations on how to get equivalent functionality without running them inside a monad? That does seem the intuitive solution, after all, as the run-time environment of the language requires state and error handling (which is what I use Either for).

Kben
  • 43
  • 4
  • Could you elaborate on the complications you ran into, please. – dave4420 Jul 20 '12 at 09:25
  • Tried to clarify the complications a bit. Problem is, I pretty much don't know where to even begin with either implementation, so I can't really elaborate much more--this is how far I've gotten so far. – Kben Jul 20 '12 at 09:50

1 Answers1

4

Your Expr data type doesn't need to be liftable in order for you to build a quasiquoter for it, and in this case, it's impossible to implement a Lift instance. The reason is that you can't "look inside" the Runtime Expr value, since a StateT values are essentially functions and there's no generic way to find out what a function does.

What you need to do is to construct the AST that builds up the Expr "by hand", i.e. without using the [| |]-quoters (or to be more specific, you can use the [| |]-quotes to help you build the AST, but you can't quote the Expr data directly).

So in essence, you need a parser of type

parseExpQ :: Parser ExpQ

which produces the Exp that represents the Haskell code needed to build the Expr. For the Fn constructor, you'd need to either build a do-block using the DoE-constructor or a bind chain using InfixE and >>=.

If this sounds too complicated, your other option is to represent the function body as an expression (or a list of statements, depending on the semantics of your toy language). I.e.

data Expr = Num Int
          | Str T.Text
          | Fn [T.Text] [Statement]

And define the Statement type so that it can be interpreted to produce the same effects that you previously used StateT Either for.

shang
  • 24,642
  • 3
  • 58
  • 86
  • Thanks, I think I get it. A separate issue I ran into though: in ghci, running anything like [toy| someExpr |] results in "parse error on input `|]'", even though I've set it up to deal with TH (runQ [| whatever |] works as expected, for instance). Any idea? – Kben Jul 20 '12 at 14:50
  • You need `{-# LANGUAGE QuasiQuotes #-}` at the top or your file or -XQuasiQuotes as a compiler option. – shang Jul 20 '12 at 14:58
  • Ah, sorry, I didn't notice you said GHCi. In GHCi you can enable language extensions by: `:set -XQuasiQuotes` – shang Jul 20 '12 at 14:59
  • Got it. I already had the language pragma, but I had to set it in ghci as well anyway. – Kben Jul 20 '12 at 15:01