Haskell:在打结的同时处理循环依赖关系
在编写具有本地类型推断功能的编程语言时(即,它能够推断除函数参数(如Scala)之外的类型),我遇到了循环依赖的问题 我通过递归地探索AST来执行类型检查/推断,并将每个可选类型的节点惰性地映射到一个类型检查节点。由于任何节点的类型都可能取决于AST中其他节点的类型,因此,我在这里打了个结,以便在推断/检查当前节点的类型时可以引用其他节点的类型(我将类型化的AST保留在读卡器monad的环境中) 这在典型情况下工作得很好,但由于循环依赖关系而崩溃,因为程序无休止地跟随循环以搜索已知类型 这类问题的解决方案通常(据我所知)是维护一个已探索节点的集合,但我想不出一种在打结时进行此操作的引用透明方式,因为我事先不知道访问/评估节点的顺序,因为这取决于它们之间的依赖关系图 因此,我似乎需要维护一个本地的、可变的节点集合。为此,我尝试了以下方法:Haskell:在打结的同时处理循环依赖关系,haskell,type-inference,cyclic-dependency,tying-the-knot,st-monad,Haskell,Type Inference,Cyclic Dependency,Tying The Knot,St Monad,在编写具有本地类型推断功能的编程语言时(即,它能够推断除函数参数(如Scala)之外的类型),我遇到了循环依赖的问题 我通过递归地探索AST来执行类型检查/推断,并将每个可选类型的节点惰性地映射到一个类型检查节点。由于任何节点的类型都可能取决于AST中其他节点的类型,因此,我在这里打了个结,以便在推断/检查当前节点的类型时可以引用其他节点的类型(我将类型化的AST保留在读卡器monad的环境中) 这在典型情况下工作得很好,但由于循环依赖关系而崩溃,因为程序无休止地跟随循环以搜索已知类型 这类问题
- 使用状态monad失败,因为每个子计算似乎都接收到自己的状态副本,因此无法在计算的不同分支之间共享有关已探索节点的信息
- 将IO monad与IORefs一起使用,由于其严格性,这使我无法打结
- 在IORefs中使用
,这会导致突变无序或根本不发生的问题unsafePerformIO
- 将ST单子与STRefs一起使用,这在严格性方面引入了与IO单子相同的问题
unsafeInterleaveST
在AST上进行映射时强制执行惰性求值,这很有效,但感觉很脆弱
是否有一个更惯用和/或更具参考价值的解决方案,而不是冗长或复杂?我本来会包括一个代码示例,但我对这个问题的最简单表述是大约250行。我不知道有任何实际的类型检查器使用打结。我认为这不会让你走得很远。约束/元变量的状态是通常的实现,或简单情况下的双向类型检查。@András-Kovács我以前从未编写过编译器,但打结实现在我看来是一个合适的解决方案,因为它可以工作。我很想了解你提到的其他方法,谢谢你的发帖!扩展“约束”解决方案:当遇到相互递归块时,例如
let x=y;y=x in.
,您可以为所有变量指定一个新类型<代码>x:t0;y:t1并按顺序检查每个绑定。当推断x=y
的类型时,您会发出一个等式约束t0~t1
,并继续(不递归推断y
的类型,因为它是同一相互块的一部分);然后,通过发出约束t1~t0
,以相同的方式推断y=x
。最后,您必须解决(琐碎的)统一问题t0~t1/\t1~t0
;一种解决方案是t0->t0;t1->t0
。就打结实现而言,我刚刚发现了Control.Monad.ST.Lazy
,它省去了unsafeInterleaveST
的需要,工作起来很有魅力:)我不知道有任何实际的类型检查器使用打结。我认为这不会让你走得很远。约束/元变量的状态是通常的实现,或简单情况下的双向类型检查。@András-Kovács我以前从未编写过编译器,但打结实现在我看来是一个合适的解决方案,因为它可以工作。我很想了解你提到的其他方法,谢谢你的发帖!扩展“约束”解决方案:当遇到相互递归块时,例如let x=y;y=x in.
,您可以为所有变量指定一个新类型<代码>x:t0;y:t1并按顺序检查每个绑定。当推断x=y
的类型时,您会发出一个等式约束t0~t1
,并继续(不递归推断y
的类型,因为它是同一相互块的一部分);然后,通过发出约束t1~t0
,以相同的方式推断y=x
。最后,您必须解决(琐碎的)统一问题t0~t1/\t1~t0
;一种解决方案是t0->t0;t1->t0
。就打结实现而言,我刚刚发现了Control.Monad.ST.Lazy
,它省去了unsafeInterleaveST
的需要,并且工作起来很有魅力:)