此代码编译良好:
data None = None { _f :: Int }
type Simpl = Env
type Env = Int
但是,我在使用此代码时遇到错误:
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
data None = None { _f :: Int }
type Simpl = Env
makeLenses ''None
type Env = Int
错误:
Not in scope: type constructor or class `Env'
我刚在类型声明之间添加了一行makeLenses ''None
这意味着TemplateHaskell代码可以改变构造函数的范围吗?
有谁知道有关此问题的详细信息(或如何避免此问题)?
答案 0 :(得分:14)
如果您按如下方式重新排序代码,则可以:
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
data None = None { _f :: Int }
type Simpl = Env
type Env = Int
makeLenses ''None
当您使用Template Haskell接头向代码中添加新的顶级声明时,正如makeLenses
所做的那样,代码中的声明顺序突然变得重要!
原因是通常编译Haskell程序涉及首先收集所有顶级声明并在内部对它们进行重新排序以将它们按顺序排列,然后逐个编译它们(或逐个组地进行相互递归声明)。
通过运行任意代码引入新声明,因为GHC不知道可能需要运行哪些声明makeLenses
,并且它也不知道它将产生哪些新声明。所以它不能把整个文件放在依赖顺序中,只是放弃并期望用户自己做,至少是为了决定声明应该在拼接之前还是之后进行。
我能找到的唯一在线参考解释了这一点,在original Template Haskell paper第7.2节中,它说该算法是:
- 将声明分组如下:
[d1,...,da]
splice ea
[da+2,...,db]
splice eb
...
splice ez
[dz+2,...,dN]
其中唯一的拼接声明是明确指出的声明,因此每个组
[d1,...,da]
等都是普通的Haskell声明。
- 在第一组上执行常规依赖性分析,然后进行类型检查。它的所有自由变量都应该在范围内。
所以这里的问题是拼接之前的第一组声明在拼接之后被单独处理到第二组,并且它看不到Env
的定义。
我的一般经验法则是尽可能将这样的拼接放在文件的底部,但我认为这并不能保证它始终有效。