Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Splicing type signature in template haskell

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>
like image 234
Hashmush Avatar asked Aug 28 '15 17:08

Hashmush


3 Answers

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. :-(

like image 163
luqui Avatar answered Oct 16 '22 06:10

luqui


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.

like image 28
Rudy Matela Avatar answered Oct 16 '22 06:10

Rudy Matela


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 |]
like image 1
ntc2 Avatar answered Oct 16 '22 07:10

ntc2