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