模板Haskell:GHC阶段限制以及如何克服

时间:2013-05-02 21:41:01

标签: haskell template-haskell

我在模块中有以下代码:

{-# LANGUAGE TemplateHaskell #-}

module Alpha where

import Language.Haskell.TH
import Data.List

data Alpha = Alpha { name :: String, value :: Int } deriving (Show)
findName n = find ((== n) . name)

findx obj = sequence [valD pat bod []]
    where
        nam = name obj
        pat = varP (mkName $ "find" ++ nam)
        bod = normalB [| findName nam |]

然后我在主文件中有以下内容:

{-# LANGUAGE TemplateHaskell #-}

import Alpha

one   = Alpha "One" 1
two   = Alpha "Two" 2
three = Alpha "Three" 3
xs    = [one, two , three]

findOne = findName "One"
findTwo = findName "Two"

$(findx three) -- This Fails
$(findx (Alpha "Four" 4)) -- This Works

main = putStrLn "Done"

我希望$(findx three)为我创建findThree = findName "Three"。但相反,我得到了这个错误:

GHC stage restriction: `three'
  is used in a top-level splice or annotation,
  and must be imported, not defined locally
In the first argument of `findx', namely `three'
In the expression: findx three

我如何克服这个问题?我宁愿不必在单独的文件中定义onetwo等。

第二个问题是$(findx (Alpha "Four" 4))为什么没有问题?

1 个答案:

答案 0 :(得分:6)

我自己并不是很熟悉模板Haskell,但基于我有限的理解,问题是当GHC尝试编译three$(findx three)在某种意义上“仍然被定义”,而$(findx (Alpha "Four" 4))的所有组成部分都已完全定义。

根本问题是同一模块中的所有定义会影响彼此的含义。这是由于类型推断以及相互递归。定义x = []可能意味着许多不同的东西,取决于上下文;它可以将x绑定到Int列表,或IO ()列表或其他任何内容。 GHC可能必须处理整个模块以确切地确定做什么意味着什么(或者它实际上是一个错误)。

模板Haskell发送到正在编译的模块中的代码必须通过该分析来考虑。所以这意味着模板Haskell代码必须在之前运行 GHC已经弄清楚模块中的定义是什么意思,所以逻辑上你不能使用它们中的任何一个。

当GHC编译 模块时,已经完全检查了从其他模块OTOH导入的内容。通过编译此模块,不再需要了解有关它们的信息。因此,在编译本模块中的代码之前,可以访问和使用这些文件。

考虑它的另一种方式:也许three实际上不应该是Alpha类型。也许这是一个错字,构造函数应该是Alphz。通常,GHC通过编译使用three的模块中的所有其他代码来查明是否存在不一致,从而发现了这些类型的错误。但是,如果 代码使用或仅被$(findx three)发出的内容使用,该怎么办?我们甚至不知道在运行之前会有什么代码,但我们无法解决three在运行之前是否正确输入的问题。

当然,可能在某些情况下稍微解除这个限制(我不知道它是否容易或实用)。也许我们可以让GHC认为某些东西要么“早期定义”,如果是导入的,或者它只使用“早期定义”的其他东西(并且可能具有明确的类型签名)。也许它可以尝试在不运行TH代码的情况下编译模块,如果它在遇到任何错误之前设法完全进行类型检查three,它可以将其提供给TH代码,然后重新编译所有内容。缺点(除了所涉及的工作)将使得更复杂地说明你可以传递给模板Haskell的确切限制。