th-compat

This package defines a Language.Haskell.TH.Syntax.Compat
module, which backports the Quote and Code types to
work across a wide range of template-haskell versions.
On recent versions of template-haskell (2.17.0.0 or
later), this module simply reexports Quote and Code
from Language.Haskell.TH.Syntax. Refer to the Haddocks
for Language.Haskell.TH.Syntax.Compat for examples of
how to use this module.
Quick Start Guide
Let’s say you have a library that offers a foo :: Q (TExp a),
you want to make it compatible with the new Code type,
and you intend that foo is spliced directly in to user code.
Use SpliceQ as a type alias for the return of your
function. This is Q (TExp a) prior to GHC 9, and Code Q a
after. This allows your code to be spliced in regardless of
GHC version.
Use liftSplice to convert a m (TExp a) into a Splice m a.
Use examineSplice before typed quoters. This will allow
a typed quasiquotation to work regardless of GHC version.
When splicing in a TExp a value into a typed quoter, use expToSplice.
For a real life example, consider this conversion, from this PR:
discoverInstances
:: forall c. (Typeable c)
=> Q (TExp [SomeDict c])
discoverInstances = do
let className = show (typeRep (Proxy @c))
instanceDecs <- reifyInstances (mkName className) [VarT (mkName "a")]
dicts <- fmap listTE $ traverse decToDict instanceDecs
[|| concat $$(pure dicts) ||]
listTE :: [TExp a] -> TExp [a]
listTE = TExp . ListE . map unType
decToDict :: InstanceDec -> Q (TExp [SomeDict c])
With GHC 9, this will have the following problems:
reifyInstances operates in Q, not Code, so it will not type check with the [|| concat $$(pure dicts) ||] line.
- We cannot call
pure in Code, since Code is not an applicative.
- Typed quasiquotes return a
Quote m => Code m a, not Q (TExp a).
To fix these problems, we make the following diff:
discoverInstances
:: forall c. (Typeable c)
- => Q (TExp [SomeDict c])
+ => SpliceQ [SomeDict c]
- discoverInstances = do
+ discoverInstances = liftSplice $ do
let className = show (typeRep (Proxy @c))
instanceDecs <- reifyInstances (mkName className) [VarT (mkName "a")]
dicts <- fmap listTE $ traverse decToDict instanceDecs
- [|| concat $$(pure dicts) ||]
+ examineSplice [|| concat $$(expToSplice dicts) ||]
The above pattern should work to ensure that code is compatible across a wide range of GHC versions.