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...)

Était-ce utile?

La 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]

Autres conseils

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top