Quasiquator expression for AST, where does one constructor perform monadic computation?

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 never used Template Haskell for anything, before I decided that it would be convenient to have a quasi-cycler for my toy language, so I admit that I can skip something obvious.

But in any case, I started to play a little with him, followed some textbooks, etc. and basically discovered that everything but dealing with the Fn constructor was easy.

During my internet gowns on the Internet, I found two common ways that people write an expression:

  • Creating your Expr data Expr is an instance of TH: s Lift and just [| quote |] expression that parsed
  • Output Data and Typeable to their Expr equivalent, and then apply dataToExpQ to the same parsing result

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

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

(I managed to implement the instance for Data.Text myself, though).

I believe the real problem is that I just don't know TH well enough yet, but so far no number of tutorials or examples have helped me deal with this anywhere.

In the second case, the problem was that for Expr for the Data instance there should also be

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

Then my question is, is there an easy way to do this? Or maybe 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 the monad? In the end, this seems like an intuitive solution, as the runtime for the language requires state and error handling (for which I use Either for).

+4
source share
1 answer

The Expr data Expr does not need to be raised so that you can build a quasiquater for it, in which case it is not possible to implement an Lift instance. The reason is that you cannot “peer inside” the Runtime Expr , since StateT values ​​are essentially functions and there is no general way to find out what the function does.

What you need to do is build an AST that creates the Expr “manually”, i.e. without using [| |] -quoters (or, more specifically, you can use [| |] -quotes to help you build an AST, but you cannot directly specify Expr data).

So essentially you need a type parser

 parseExpQ :: Parser ExpQ 

which creates Exp , which represents the Haskell code needed to build Expr . For the Fn constructor, you need to either build the do block using the DoE -constructor constructor, or the binding chain using InfixE and >>= .

If this sounds too complicated, another option is to present the body of the function 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 type of Statement so that it can be interpreted to get the same effects that you previously used StateT Either for.

+4
source

All Articles