模板Haskell:GHC阶段限制及克服方法

模板Haskell:GHC阶段限制及克服方法,haskell,template-haskell,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 [

我在模块中有以下代码:

{-# 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
我如何克服这个问题?我宁愿不必在一个单独的文件中定义一个、两个等


第二个问题是为什么
$(findx(Alpha“Four”4))
工作没有问题?

我自己也不是很了解Haskell,但基于我有限的理解,问题是当GHC试图编译
$(findx three)
时,
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的内容有哪些确切限制会变得更加复杂。

如果您只是尝试在定义的同一模块中引用名称,您可能会以某种方式通过引用/准引用来做到这一点?如果您试图在拼接中实际执行某些操作,请忘记它。编译器根本不能这么做。
$(findx-three)
$(findx(Alpha“Four”4))
之间有什么区别?前者实际上会导致错误,但后者会起作用。
three
在同一模块中定义,而
Alpha
则不是。对于路过的人,有趣的功能可以帮助解决某些舞台限制问题。另请参阅。我认为,对传递给模板Haskell的内容的限制实际上不会复杂得多,但编码可能非常困难。