Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Template Haskell, how can I splice the same type into multiple locations?

I'm defining instances of classes from vector-space for the OpenGL types, and to spare my typing muscles, I want to use Template Haskell to write a bunch of the instances for me.

I started out small by defining function to derive instances for AdditiveGroup:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
module Data.VectorSpace.OpenGL.TH where

import Control.Applicative
import Control.Monad
import Data.AdditiveGroup
import Data.VectorSpace

import Language.Haskell.TH

deriveScalarAdditive ts = concat <$> forM (map conT ts) (\t -> [d| 
    instance AdditiveGroup $t where zeroV = 0; (^+^) = (+); negateV = negate
  |])

This works fine, but note that I'm only splicing $t once into the Oxford brackets. Now, the function to derive VectorSpace instances:

deriveScalarVectorSpace ts = concat <$> forM (map conT ts) (\t -> [d|    
    instance VectorSpace $t where type Scalar $t = $t; (*^) = (*)
  |])

But, this barfs:

Type indexes must match class instance head
Found `t_tt' but expected `t_ts'
In the associated type instance for `Scalar'
In the instance declaration for `VectorSpace $t'
In the Template Haskell quotation
  [d| instance VectorSpace $t where
          type instance Scalar $t = $t
          { *^ = (*) } |]

The difference between t_ts and t_tt in the error tells me that TH is creating a new, unique name every time I splice in $t, when of course the definition will only work if those types are the same.

Is there a way to get the behavior I want with Oxford brackets, or will I have to fall back to good old lexical scope and the Language.Haskell.TH combinators? I know this would probably be easier with the CPP, but I want to take this opportunity to learn some TH.

like image 515
acfoltzer Avatar asked Dec 07 '11 05:12

acfoltzer


1 Answers

I think you'll have to use the Language.Haskell.TH combinators. See the following tickets:

  • GHC 4230
  • GHC 4135

Doing so is very straightforward. I would start with this (slightly formatted)

*Foo Language.Haskell.TH> runQ (deriveScalarAdditive [''Int] ) >>= print

[InstanceD [] (AppT (ConT Data.AdditiveGroup.AdditiveGroup) (ConT GHC.Types.Int))
  [ValD (VarP zeroV_12) (NormalB (LitE (IntegerL 0))) [],
   ValD (VarP ^+^_13) (NormalB (VarE GHC.Num.+)) [],
   ValD (VarP negateV_14) (NormalB (VarE GHC.Num.negate)) []]
]

from here, it's pretty simple to see how to construct instances using the combinators. Also note that you can mix quoting with TH, a quoted expression [| some code |] :: ExpQ, which is often useful to create function bodies.

like image 70
John L Avatar answered Oct 29 '22 17:10

John L