Backward- (and forward-)compatible Quote and Code types.
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. The makeRelativeToProject
utility is also backported. On recent versions of template-haskell
(2.17.0.0 or later), this module simply reexports definitions from Language.Haskell.TH.Syntax
. Refer to the Haddocks for Language.Haskell.TH.Syntax.Compat
for examples of how to use this module.
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 inQ
, notCode
, so it will not type check with the[|| concat $$(pure dicts) ||]
line.- We cannot call
pure
inCode
, sinceCode
is not an applicative. - Typed quasiquotes return a
Quote m => Code m a
, notQ (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.