packages feed

th-compat 0.1.2 → 0.1.3

raw patch · 4 files changed

+179/−6 lines, 4 filesdep ~base-compatdep ~mtldep ~template-haskell

Dependency ranges changed: base-compat, mtl, template-haskell, transformers

Files

CHANGELOG.md view
@@ -1,3 +1,7 @@+### 0.1.3 [2021.08.29]+* Implement `qGetDoc` and `qPutDoc` in the `Quasi` instance for `QuoteToQuasi`.+* Add `expToSplice`.+ ### 0.1.2 [2021.03.12] * Add `bindSplice`, `bindSplice_`, `examineSplice`, `joinSplice`,   `hoistSplice`, `liftSplice`, and `unTypeSplice` to
README.md view
@@ -23,3 +23,67 @@ 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](https://github.com/parsonsmatt/discover-instances/pull/2):++```haskell+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:++1. `reifyInstances` operates in `Q`, not `Code`, so it will not type check with the `[|| concat $$(pure dicts) ||]` line.+2. We cannot call `pure` in `Code`, since `Code` is not an applicative.+3. Typed quasiquotes return a `Quote m => Code m a`, not `Q (TExp a)`.++To fix these problems, we make the following diff:++```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.
src/Language/Haskell/TH/Syntax/Compat.hs view
@@ -67,7 +67,8 @@   , bindCode_   , joinCode -  -- * @Splice@+  -- * Compatibility with @Splice@s+  -- $splice   , Splice   , SpliceQ   , bindSplice@@ -79,6 +80,7 @@   , liftTypedFromUntypedSplice   , unsafeSpliceCoerce   , unTypeSplice+  , expToSplice #endif   ) where @@ -440,6 +442,10 @@ #if MIN_VERSION_template_haskell(2,16,0)   qReifyType          = qtqError "qReifyType" #endif+#if MIN_VERSION_template_haskell(2,18,0)+  qGetDoc             = qtqError "qGetDoc"+  qPutDoc             = qtqError "qPutDoc"+#endif  ------------------------------------------------------------------------------- -- Code@@ -684,6 +690,42 @@ joinCode = flip bindCode id # endif +-- $splice+--+-- This section of code is useful for library authors looking to provide+-- a typed @TemplateHaskell@ interface that is backwards- and+-- forward-compatible. This section may be useful for you if you+-- specifically intend for the splice to be done directly.+--+-- Prior to GHC 9, you'd offer a value with type @'Q' ('Syntax.TExp' a)@.+-- After GHC 9, these values are no longer acceptable in a typed splice:+-- typed splices must operate in @Code m a@ instead.+--+-- The @'Splice' m a@ type is used to work with both versions - it is a type+-- alias, and depending on the version of @template-haskell@ that was+-- compiled, it will either be @'Code' m a@ or @m ('Syntax.TExp' a)@.+--+-- The function 'liftSplice' can be used to convert a @'Q' ('Syntax.TExp' a)@+-- expression into a @'Code' 'Q' a@ expression in a compatible manner - by+-- lifting to 'SpliceQ', you get the right behavior depending on your+-- @template-haskell@ version.+--+-- The function 'examineSplice' can be used on typed QuasiQuoters, and the+-- result will be converted into an appropriate @m ('Syntax.TExp' a)@. This+-- allows you to use typed quasiquoters in a @do@ block, much like+-- 'examineCode' does with 'Code'.+--+-- With 'expToSplice', you can substitute uses of 'pure' when given the+-- specific type:+--+-- @+-- pureTExp :: 'Syntax.TExp' a -> 'Q' ('Syntax.TExp' a)+-- pureTExp = pure+-- @+--+-- This allows you to splice @'Syntax.TExp' a@ values directly into a typed+-- quasiquoter.+ -- | @'Splice' m a@ is a type alias for: -- -- * @'Code' m a@, if using @template-haskell-2.17.0.0@ or later, or@@ -763,6 +805,68 @@ # else bindSplice_ q c = liftSplice ( q >> examineSplice c) # endif++-- | Lift a @'Syntax.TExp' a@ into a 'Splice'. This is useful when splicing+-- in the result of a computation into a typed QuasiQuoter.+--+-- One example is 'traverse'ing over a list of elements and returning an+-- expression from each element.+--+-- @+-- mkInt :: 'String' -> 'Q' ('Syntax.TExp' 'Int')+-- mkInt str = [|| length $$str ||]+--+-- mkInts :: ['String'] -> 'Q' ['Syntax.TExp' 'Int']+-- mkInts = traverse mkInt+-- @+--+-- This gives us a list of 'Syntax.TExp', not a 'Syntax.TExp' of a list. We+-- can push the list inside the type with this function:+--+-- @+-- listTE :: ['Syntax.TExp' a] -> 'Syntax.TExp' [a]+-- listTE = 'Syntax.TExp' . 'Syntax.ListE' . 'map' 'Syntax.unType'+-- @+--+-- In a @do@ block using 'liftSplice', we can bind the resulting+--+-- @'Syntax.TExp' ['Int']@ out of the expression.+--+-- @+-- foo :: 'Q' ('Syntax.TExp' Int)+-- foo = do+--      ints <- mkInts ["hello", "world", "goodybe", "bob"]+--      [|| sum $$(pure (listTE ints)) ||]+-- @+--+-- Prior to GHC 9, with the 'Q' type, we can write @'pure' :: 'Syntax.TExp' a -> 'Q' ('Syntax.TExp' a)@,+-- which is a valid thing to use in a typed quasiquoter.+-- However, after GHC 9, this code will fail to type check. There is no+-- 'Applicative' instance for @'Code' m a@, so we need another way to+-- splice it in.+--+-- A GHC 9 only solution can use @'Code' :: m ('Syntax.TExp' a) -> Code+-- m a@ and 'pure' together, like: @'Code' . 'pure'@.+--+-- With 'expToSplice', we can splice it in a backwards compatible way.+-- A fully backwards- and forwards-compatible example looks like this:+--+-- @+-- mkInt :: 'String' -> 'Q' 'Int'+-- mkInt str = 'examineSplice' [|| length $$str ||]+--+-- mkInts :: ['String'] -> 'Q' ['Syntax.TExp' 'Int']+-- mkInts = traverse mkInt+--+-- foo :: 'SpliceQ' 'Int'+-- foo = 'liftSplice' $ do+--      ints <- mkInts ["hello", "world", "goodybe", "bob"]+--      'examineSplice' [|| sum $$(expToSplice (listTE ints)) ||]+-- @+--+-- @since ????.??.??+expToSplice :: Applicative m => Syntax.TExp a -> Splice m a+expToSplice a = liftSplice $ pure a  -- | A variant of 'examineCode' that takes a 'Splice' as an argument. Because -- this function takes a 'Splice' as an argyment, the type of this function
th-compat.cabal view
@@ -1,6 +1,6 @@ cabal-version:       >=1.10 name:                th-compat-version:             0.1.2+version:             0.1.3 synopsis:            Backward- (and forward-)compatible Quote and Code types description:         This package defines a "Language.Haskell.TH.Syntax.Compat"                      module, which backports the @Quote@ and @Code@ types to@@ -30,8 +30,9 @@                    , GHC == 8.4.4                    , GHC == 8.6.5                    , GHC == 8.8.4-                   , GHC == 8.10.4+                   , GHC == 8.10.7                    , GHC == 9.0.1+                   , GHC == 9.2.* extra-source-files:  CHANGELOG.md, README.md  source-repository head@@ -41,7 +42,7 @@ library   exposed-modules:     Language.Haskell.TH.Syntax.Compat   build-depends:       base             >= 4.3 && < 5-                     , template-haskell >= 2.5 && < 2.18+                     , template-haskell >= 2.5 && < 2.19   if !impl(ghc >= 8.0)     build-depends:     fail             == 4.9.*                      , transformers     >= 0.2 && < 0.6@@ -57,10 +58,10 @@   other-modules:       Language.Haskell.TH.Syntax.CompatSpec                        Types   build-depends:       base             >= 4.3 && < 5-                     , base-compat      >= 0.6 && < 0.12+                     , base-compat      >= 0.6 && < 0.13                      , hspec            >= 2   && < 3                      , mtl              >= 2.1 && < 2.3-                     , template-haskell >= 2.5 && < 2.18+                     , template-haskell >= 2.5 && < 2.19                      , th-compat   build-tool-depends:  hspec-discover:hspec-discover >= 2   hs-source-dirs:      tests