[Template-haskell] splicing types

Robert Greayer robgreayer at yahoo.com
Mon Jun 30 23:14:48 EDT 2008


Hi,

I've been using TH to implement a library that generates code in a 'foreign' language that corresponds to a haskell ADT, as well as the haskell & foreign code required to transmit (i.e. serialize and deserialize) values back and forth between a haskell program and the foreign program. I've run into some situations in which it would be useful for there to be a way to control the code generation declaratively by providing additional meta-data to the code generator, beyond just the ADT(s) themselves.  For example, suppose in a group of ADTs, certain fields should be omitted when serialized, and therefore not represented in the 'foreign' types, e.g.:

> data Bar {
>         val1 :: String,
>         val2 :: Int -- omit this in serialization
>     }

As a 'first order' stab at handling this issue, I thought of using type aliases, e.g

> type Omitted a = a
...
>         val2 :: Omitted Int

which could be generalized to a sort of 'annotation type', e.g.

> type Ann a b = a
> type Omitted = () -- a particular annotation
...
>        val2 ::  Ann Int Omitted

you could have multiple annotations, e.g.
>        val2 :: Ann Int (Omitted,Ann1,Ann2) -- an Int with three annotations

The code generator 'sees' the aliases, and can therefore act on the ones it understands.

This works, but I immediately run into situations where more flexibility is needed.  Indeed, in the example I've chose ('omit'), all works ok when serializing the data, but what about *de* serializing (in haskell)?  Some value needs to be chosen for the omitted field.  An arbitrary default of (say) zero may not be appropriate.  What I'd really like to be able to do is parameterize my annotations with values.  'Omitted' would be parameterized with the default value to use, e.g.

> type Omitted a = ()
...
>        val2 :: Ann Int (Omitted 1000)

But of course this doesn't work, as I'm dealing with types here, not expressions.  I could do something extremely hokey (hokier even than this system of annotations), like:

> type I1000 = ()
...
>        val2 :: Ann Int (Omitted I1000) -- read as 'omitted, with default of 1000'

The code generator would determine the default value based on the name of the type 'Omitted' is applied to.  The user of the annotation system would have the ugly task of creating 'dummy' type aliases for any values that need passing (and it would be quite cumbersome to attempt a naming convention for strings, etc.)

It's at this point that I had the (for me) clever idea of simply writing a 'type level encoder' for any haskell expression, and then splicing in the encoding at the appropriate point, e.g. my integer as a tuple-type of binary digits: (One,Zero,Zero,One) would be a type encoding of '9', given the existence of type (aliases) such as:

> type One = ()
> type Zero = ()

A function to encode an integer into the TH.Syntax representation of such a tuple --

(AppT (AppT (AppT (AppT (TupleT 4) (ConT ''One)) (ConT ''Zero)) (ConT ''Zero)) (ConT ''One)) 

is fairly trivial, as is a function to decode it back into an integer.  It should (I think) be possible to write a pair of functions:

> encT :: Q Exp -> Q Type
> decT :: Q Type -> Q Exp

to encode/decode arbitrary expressions into/from types.

which would then allow my annotations to look like:

>         val2 :: Ann Int (Omitted $(encT [|1000|]))

and allow me to annotate types with just about anything else, as well, with the ugly details of encoding/decoding hidden from both the user of the annotation system, and any code generation system that makes use of annotations.  Which I found quite pleasing, since this 'quasi-annotations' feature is probably more powerful than some similar, 'intentional' annotation features of other languages, despite (afaik) not being an envisaged use of the language features involved.

But then I tried it out and found that I'm trying to splice into what is currently an invalid splice position for TH.  (Indeed, on the very day that I decided to try my idea out concretely, the relevant ticket affecting type declarations as valid splice positions was downgraded to low priority, and disincluded from GHC 6.10 -- http://hackage.haskell.org/trac/ghc/ticket/1476).  So I was wondering if 1) splicing types is inseparable from the  (harder?) problem of splicing patterns and 2) if splicing types is something that will is likely at some point to be implemented (is it that a good solution isn't yet apparent, or that it's just not important enough for the goals of TH?) and 3) has this idea, or something similar, been explored (regardless of the fact that it won't work with TH as-implemented) before?

Thanks,
rcg


      


More information about the template-haskell mailing list