4

The following introduction is provided to ensure you understand how I reached the problem (to not fall prey to the XY problem):

I am working on a program which turns a parser in Parsec-like DSL into an actual LL(1) parser (and in the future similarly for LALR(1) or others).

It basically works as follows:

  1. The DSL consists of functions which together build up a GADT. The result is a datatype which might contain cycles ('tying the knot'-style).
  2. Data.Reify is used to turn this into a graph representation ('untying' the knot).
  3. Perform the necessary transformations on this graph to turn it into a LL(1) parsing table.
  4. Construct the parser which will use this table.

Because we want to be able to use the data that is recognized while parsing to construct some kind of result, we need to pass on functions through steps (1.) to (4.). In steps 1, 2, 3 we can get away with using an existential datatype. It's only when actually running the parser, that I found myself requiring Data.Dynamic and its dynApp to combine the results. We know that the types line up (since in step (1) the GADT construction is type-checked), but I did not figure out how to use the existential types in any other way (as each of the parsing steps might have a very different type).

The current procedure thus 'works' but requires Dynamic. Also, the whole parser, while based on a written function definition, will be constructed at runtime. Enter Template Haskell: Since the parser function is defined in a different module, it ought to be able to construct the parser at compile-time.


However, there is no Lift instance for Dynamic!

Furthermore, attempting to directly lift the existential types (i.e. require a Lift constraint on them) instead also does not work, as these are almost always functions!

How can we lift a GADT containing either Dynamics or Typeable a => a's into a TemplateHaskell quotation? Or is there another approach to be able to handle this situation?

Qqwy
  • 5,214
  • 5
  • 42
  • 83
  • 1
    In the general case... No. You can't `lift` a function into TH. They just don't support the kind of introspection that would be necessary for that. Any plausible solution is going to depend on the `Lift` class, and any existential will need to pack up instances of that as well as `Typeable`, so `Dynamic` is out. – Carl Aug 11 '21 at 00:07
  • 1
    Instead of functions, can you design a *second* DSL, which can be evaluated to the functions you wanted to pass around, but which itself is just plain old data? e.g. instead of passing around `map (+1)`, can you write `data Function a b where Map :: Function (Function a b) (Function [a] [b]); Plus :: Function Int (Function Int Int); PartialApp :: Function a (Function b c) -> a -> Function b c` and pass around `PartialApp Map (PartialApp Plus 1)`? – Daniel Wagner Aug 11 '21 at 06:17
  • (...or better: `data Term a where Map :: Term ((a -> b) -> [a] -> [b]); Plus :: Term (Int -> Int -> Int); Lit :: Lift a => a -> Term a; App :: Term (a -> b) -> Term a -> Term b`, and then pass around `App Map (App Plus (Lit 1))`.) – Daniel Wagner Aug 11 '21 at 06:23
  • Thank you for your replies! It feels like a big limitation of TH that when given a (datastructure containing a) value like `(\x -> x + 1)`, that this cannot be turned into `[| \x -> x + 1 |]`. – Qqwy Aug 11 '21 at 11:38
  • @DanielWagner that would make it impossible for users to interface with arbitrary existing Haskell code, which would be the point of creating a parsing DSL. In a perfect world, the library would be a drop-in replacement for `Parsec`. – Qqwy Aug 11 '21 at 11:41
  • 2
    Maybe [compiling to categories](http://conal.net/papers/compiling-to-categories/) would help? A GHC plugin that turns functions into a CCC data structure. – Ari Fordsham Aug 13 '21 at 09:22
  • Instead of lifting arbitrary code into TH you can ask the user to write their functions in TH quotations, so let the user write `[| \x -> x + 1 |]` (or better: `[|| \x -> x + 1 ||]`), this is the approach used by many staged libraries. Of course this has the disadvantage of a little bit of extra overhead for the user. – Noughtmare Aug 14 '21 at 18:48

0 Answers0