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.