8

我在模块中有以下代码:

{-# 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))工作没有问题?

4

1 回答 1

8

我自己对 Template Haskell 不是很了解,但根据我有限的理解,问题是three在 GHC 尝试编译时在某种意义上“仍在定义” $(findx three),而所有组件$(findx (Alpha "Four" 4))都已经完全定义。

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

该分析必须考虑 Template Haskell 发出到正在编译的模块中的代码。这意味着必须在GHC 弄清楚模块中定义的含义之前运行 Template Haskell 代码,因此从逻辑上讲,您不能使用它们中的任何一个。

当 GHC 编译该模块时,已经完全检查了从其他模块 OTOH 导入的内容。通过编译此模块,无需了解更多关于它们的信息。所以这些可以在编译这个模块中的代码之前访问和使用。

另一种思考方式:也许three实际上不应该是 type Alpha。也许那是一个错字,构造函数应该是Alphz. 通常,GHC 通过编译模块中的所有其他代码来发现这些错误,这些代码three用于查看是否引入了不一致。但是,如果代码使用或被仅由 发出的东西使用$(findx three)呢?在我们运行之前,我们甚至不知道会是什么代码,但是直到我们运行它之后,我们才能解决是否three正确键入的问题。

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

于 2013-05-04T00:34:29.433 回答