10

I'm trying to create a type signature for a function in template haskell. Is there an easy way of doing this?

I've done some workarounds to solve it in the meantime, but it should be easier, right?

-- TH.hs
module Lib.TH (mkFunction) where

import Language.Haskell.TH

mkFunction n = do
  let name = mkName n
  [d|
    $( ... ) :: Integer -> Integer
    $(varP name) = \x -> x + 2|]

-- Other.hs
import TH

mkFunction "test"

What should I write in the $( ... ) above? Everything I've tried results in

Invalid type signature: ... :: Integer -> Integer
Should be of form <variable> :: <type>
Hashmush
  • 1,973
  • 2
  • 11
  • 12
  • 1
    This may not be satisfying but you can do `$(varP name) = (\x -> x + 2) :: Integer -> Integer` – luqui Aug 28 '15 at 20:11
  • I've tried, but unfortunately I need a constraint `(T a) => a -> a`, and it doesn't work if I include that. – Hashmush Aug 29 '15 at 14:25
  • 1
    I submitted a GHC feature request to add support for splicing `Name`s into type signatures in TH decl quotes: https://ghc.haskell.org/trac/ghc/ticket/15298 – ntc2 Jun 21 '18 at 18:59

3 Answers3

4

I am no TH expert, but I found a way by digging around in the docs and following the type errors.

import Language.Haskell.TH
import Control.Applicative ((<$>))

mkFunction n = do
    let name = mkName n
    [d|
        $( return . SigD name <$> [t| Integer -> Integer |] )
        $(varP name) = \x -> x + 2 |]

I don't know if there is a cleaner way.

NOTE this works on 7.8.3 but not 7.10.2. :-(

luqui
  • 59,485
  • 12
  • 145
  • 204
  • 3
    With 7.10.2, it throws this compile error: `Splices within declaration brackets not (yet) handled by Template Haskell` ? – Sibi Aug 28 '15 at 20:31
  • @Sibi, that's too bad. Worksforme at 7.8.3, a regression perhaps. Anyway I edited my answer with an alternative phrasing that might work around this? Can you try? – luqui Aug 28 '15 at 20:33
  • @Sibi, Nuts, I'm out of ideas. Thanks for checking. – luqui Aug 28 '15 at 20:37
2

I don't think (guess) there is a way to splice the name in a Template Haskell type signature (for GHC ≤ 7.10). To still use the quasi-quoting, you can try to process the AST afterwards to set the name where appropriate (and leave the rest untouched):

setName :: Name -> DecsQ -> DecsQ
setName = fmap . map . sn
  where sn n (SigD _ t)          = SigD n t
        sn n (ValD (VarP _) x y) = ValD (VarP n) x y
        sn _ d                   = d

mkFunction n = setName (mkName n)
  [d|
    n :: Integer -> Integer
    n = \x -> x + 2 |]

This was tested on GHC 7.10, and will probably work on past and future versions of GHC with minor modifications.

To what extent this is less or more verbose than writing the AST directly is arguable. It will depend on the frequency you write declarations like that and the complexity of the quoted code.

The above setName function will obviously break if you use it on a declaration with multiple functions (or recursive functions). To tackle that, you could write a reName function in the same spirit.

Rudy Matela
  • 6,310
  • 2
  • 32
  • 37
1

Another workaround. Using applicative syntax and refactoring @luqui's workaround you get something close to what you want:

import Language.Haskell.TH

mkFunction n = do
    let name = mkName n
    (:) <$> name `sigD` [t| Integer -> Integer |]
        <*> [d| $(varP name) = \x -> x + 2 |]
ntc2
  • 11,203
  • 7
  • 53
  • 70