Is there a nice(r) way of writing this Template Haskell code involving singleton data types?

StackOverflow https://stackoverflow.com/questions/15285016

  •  18-03-2022
  •  | 
  •  

Question

I've just started to use Template Haskell (I've finally got a use case, yay!) and now I'm cognitively stuck.

What I'm trying to do is generating a singleton datatype declaration of the form

data $V = $V deriving (Eq,Ord)

starting from a name V (hopefully starting with an uppercase character!). To be explicit, I'm trying to write a function declareSingleton of type String -> DecsQ (I should mention here I'm using GHC 7.6.1, template-haskell version 2.8.0.0) such that the splice

$(declareSingleton "Foo")

is the equivalent of

data Foo = Foo deriving (Eq,Ord)

I've got the following code working and doing what I want, but I'm not very happy with it:

declareSingleton :: String -> Q [Dec]
declareSingleton s = let n = mkName s in sequence [
        dataD (cxt []) n [] [normalC n []] [''Eq,''Ord]
   ]

I was hoping to get something like the following to work:

declareSingleton :: String -> Q [Dec]
declareSingleton s = let n = mkName s in 
    [d| data $n = $n deriving (Eq,Ord) |]

I've tried, to no avail (but not exhaustively!), various combinations of $s, $v, $(conT v), v, 'v so I have to suppose my mental model of how Template Haskell works is too simplistic.

Am I missing something obvious here, am I confusing type names and constructor names in some essential way, and can I write declareSingleton in a nice(r) way?

If so, how; if not, why not?

(Side remark: the Template Haskell API changes rapidly, and I'm happy for that - I want this simple type to eventually implement a multi-parameter type class with an associated type family - but the churn the API is currently going through doesn't make it easy to search for tutorials! There's a huge difference how TH was implemented in 6.12.1 or 7.2 (when most of the existing tutorial were written) versus how it works nowadays...)

Was it helpful?

Solution

From the Template Haskell documentation:

A splice can occur in place of

  • an expression; the spliced expression must have type Q Exp
  • an type; the spliced expression must have type Q Typ
  • a list of top-level declarations; the spliced expression must have type Q [Dec]

So e.g. constructor names simply cannot be spliced in the current version of Template Haskell.

I don't think there's much you can do to simplify this use-case (barring constructing the whole declaration as a string and transforming it into a Dec via toDec in haskell-src-meta).

You might consider simply binding the different parts of the declaration to local variables. While more verbose, it makes the code a bit easier to read.

declareSingleton :: String -> Q [Dec]
declareSingleton s = return [DataD context name vars cons derives] where
    context  = []
    name     = mkName s
    vars     = []
    cons     = [NormalC name fields]
    fields   = []
    derives  = [''Eq, ''Ord]

OTHER TIPS

a hack that parses an interpolated string rather than constructing the AST:

makeU1 :: String -> Q [Dec]
makeU1 = makeSingletonDeclaration >>> parseDecs >>> fromRight >>> return

makeSingletonDeclaration :: String -> String
makeSingletonDeclaration name = [qq|data {name} = {name} deriving (Show)|]

where:

parseDecs :: String -> Either String [Dec]

usage:

makeU1 "T"
main = print T

it needs these packages:

$ cabal install haskell-src-exts
$ cabal install haskell-src-meta
$ cabal install interpolatedstring-perl6

runnable script:

https://github.com/sboosali/haskell/blob/0794eb7a52cf4c658270e49fa4f0d9e53c4b0ebf/TemplateHaskell/Splice.hs

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top