如何在 TemplateHaskell 引用中捕获类型变量

How to capture type variable in TemplateHaskell quote

我正在尝试编写一个 Lift 实例,它不仅可以提升构造函数,还可以提升其类型变量。 例如,取 Proxy a。我需要一个 Lift 实例,这样当拼接 lift (Proxy @Int) 时,GHC 将正确推断生成的表达式是 Proxy Int.

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

我试过这个:

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

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

似乎 TH 捕获了 a 而不是预期的 Int。 我不确定还能尝试什么

/.../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

我很确定这不能通过直接引用类型来完成以使用类型应用程序。当拼接代码引用 a 时,定义它的范围已不存在。

但我相信这是导致创建类型化模板的已知问题之一 Haskell。这可以用来解决这个问题。注意GHC 8.10在class、liftTyped中引入了一个额外的条目,可以直接解决这个问题。但它也提供了在早期版本的 GHC 上使用它所需的提示,即 lift x === unTypeQ (liftTyped x).

由此,我可以看到如何为早于 8.10 的 GHC 版本为 Proxy a 编写 Lift 的实例。

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

对于 GHC 8.10,这只是

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

老实说,有一堆 Lift 个实例似乎丢失了。这只是比较间接的写法之一,至少在 GHC 8.10 之前是这样。

template-haskell 似乎没有提供类似的东西。但是可能有一个解决方案可以让您从头开始实施。这个想法是定义一个 class 来携带代表每种类型的引号:

class TLift a where
  tlift :: Q Type

例如:

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

-- and so on

然后定义一个引用类型应用程序:

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

这里的一个限制是 TLift 实例只能为完全具体的类型生成引号,不能为类型变量生成引号。也许 reflection 可以解决这个问题。