我正在编写一个小Haskell编译器,我想尽可能多地实现Haskell 2010。我的编译器可以解析模块,但是将模块完成到程序似乎是一项非常重要的任务。我编写了一些棘手但可能有效的Haskell模块的例子:
module F(G.x) where
import F as G
x = 2
此处模块F
导出G.x
,但G.x
与F.x
相同,因此模块F
会导出x
if和只有,它导出x
。
module A(a) where
import B(a)
a = 2
module B(a) where
import A(a)
在此示例中,要解析模块A
的导出,编译器必须检查从a
导入的B
是否与声明的a = 2
相同,但是{当且仅当B
导出a
时,{1}}才会导出A
。
a
在解析模块module A(f) where
import B(f)
module B(f) where
import A(f)
期间,编译器可能认为从A
导入的f
存在,这意味着B
导出A
,因此{{ 1}}可以导入f
并导出B
。唯一的问题是在任何地方都没有定义A(f)
。)。
f
此处,f
导出导致导出列表彼此依赖并依赖于它们。
所有这些示例都应该是有效的Haskell,正如Haskell 2010规范所定义的那样。
我想问一下是否有任何想法如何正确和完整地实现Haskell模块?
假设一个模块只包含(简单)变量绑定,module A(module X) where
import A as X
import B as X
import C as X
a = 2
module B(module C, C.b) where
import C
b = 3
module C(module C)
import B as C
c = 4
s(可能带有module
或import
),并导出可能合格的变量列表和as
缩写。算法必须能够:
答案 0 :(得分:9)
您可能对A Formal Specification for the Haskell 98 Module System感兴趣。
我还在一系列博客文章中介绍了一些有趣的边缘案例,截至目前只发布了first one。
最后,我正在努力 - 一个处理Haskell模块的库。它被称为haskell-names。
根据您的目标,您可以在编译器中使用它,研究源代码或贡献。 (您的示例将提供出色的测试用例。)
回答你的问题:通过计算一个固定点来处理递归模块。
您从模块图中的强连接组件开始。对于此组件中的每个模块,首先假设它不导出任何内容。然后,您将重新访问这些模块,并根据新信息计算新的导出列表。您可以证明此过程是单调的 - 每次导出列表增长(或者至少不缩小)。它迟早会停止增长 - 然后你已经达到了固定点。
您可以通过借用静态分析中的一些想法(该社区非常擅长计算固定点)来优化此算法,但我的包目前实现了朴素算法(code)。