我在模块中有以下代码:
{-# 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
我如何克服这个问题?我宁愿不必在单独的文件中定义one
,two
等。
第二个问题是$(findx (Alpha "Four" 4))
为什么没有问题?
答案 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的确切限制。