13

这段代码编译得很好:

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 代码可以改变类型构造函数的范围?

有谁知道有关此问题的详细信息(或如何避免此问题)?

4

1 回答 1

16

如果您按如下方式重新排序代码,它将起作用:

{-# 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]

其中唯一的拼接声明是明确指出的,因此每个 group[d1,...,da]等都是普通的 Haskell 声明。

  • 对第一组执行常规依赖分析,然后进行类型检查。它的所有自由变量都应该在范围内。

所以这里的问题是,拼接前的第一组声明是单独处理到拼接后的第二组的,看不到Env.

我的一般经验法则是尽可能将这样的拼接放在文件的底部,但我认为不能保证这将始终有效。

于 2014-01-02T05:44:37.580 回答