dcastro
dcastro

Reputation: 68640

How to capture type variable in TemplateHaskell quote

I'm trying to write a Lift instance that lifts not only the constructor, but also its type variables. For example, take Proxy a. I need a Lift instance such that, when lift (Proxy @Int) is spliced, GHC will correctly infer than the generated expression is a Proxy Int.

-- GHC should infer that x :: Proxy Int
x = $(TH.lift (Proxy @Int))

I tried this:

instance Lift (Proxy a) where
  lift _ = [|Proxy @a|]

x = $(TH.lift (Proxy @Int))

It seems TH captured a and not Int as expected. I'm not sure what else to try

/.../TH/Test.hs:15:7: error:
    • The exact Name ‘a’ is not in scope
        Probable cause: you used a unique Template Haskell name (NameU), 
        perhaps via newName, but did not bind it
        If that's it, then -ddump-splices might be useful

Upvotes: 3

Views: 358

Answers (2)

Li-yao Xia
Li-yao Xia

Reputation: 33389

template-haskell doesn't seem to provide anything like that. But there might be a solution that you can implement from scratch. The idea is to define a class to carry a quote representing each type:

class TLift a where
  tlift :: Q Type

For instance:

instance TLift Int where
  tlift = [t|Int|]

-- and so on

Then to define a quote featuring a type application:

proxyQ :: forall a. TLift a => Q Exp
proxyQ = [|Proxy @( $(tlift @a) )|]

One limitation here is that TLift instances can only produce quotes for completely concrete types, no type variables. Maybe reflection can work around that.

Upvotes: 2

Carl
Carl

Reputation: 27003

I'm pretty sure this can't be done with a direct reference to the type in order to use type applications. By the time the spliced code makes a reference to a, the scope in which it was defined no longer exists.

But I believe this is one of the known issues that led to the creation of typed template Haskell. And that can be used to solve this. Note that GHC 8.10 introduced an additional entry in the class, liftTyped, which can solve this directly. But it also provides the hint needed to use this on earlier versions of GHC, which is that lift x === unTypeQ (liftTyped x).

From that, I could see how to write an instance of Lift for Proxy a for versions of GHC older than 8.10.

instance Lift (Proxy a) where
    lift x = unTypeQ (helper x)
      where
        helper :: Proxy b -> Q (TExp (Proxy b))
        helper _ = [|| Proxy ||]

For GHC 8.10, this would just be

instance Lift (Proxy a) where
    liftTyped _ = [|| Proxy ||]

Honestly, there are a bunch of Lift instances seemingly missing. This is just one of the more indirect ones to write, at least before GHC 8.10.

Upvotes: 0

Related Questions