【发布时间】:2015-08-29 07:04:12
【问题描述】:
我目前正在自学 Haskell;让我们说,纯粹为了争论,我正在用 Haskell 编写一个编译器。我有一个 AST,定义如下:
data Node =
Block { contents :: [Node], vars :: Map String Variable }
| VarDecl { name :: String }
| VarAssign { name :: String, value :: Node, var :: Variable }
| VarRef { name :: String, var :: Variable }
| Literal { value :: Int }
每个Block 都是一个堆栈帧。我希望解析所有变量引用。
在一个数据可变的世界里,我这样做的方式是:
- 遍历树,跟踪最近的
Block,寻找VarDecl节点;在每一个上,我都会在最近的Block上添加一个Variable。 - 再次遍历树,寻找
VarAssign和VarRef节点。每次看到一个,我都会在堆栈帧链中查找变量,并使用相应的Variable注释 AST 节点。
现在,每当我在树上工作时,遇到VarRef,我就知道实际上是指哪个Variable。
当然,在 Haskell 中我需要一种不同的方法,因为树不是可变的。天真的方法是重写树。
declareVariables Block contents _ = Block {
contents = declareVariables contents,
vars = createVariablesFor (findVariablesInBlock contents) }
declareVariables VarAssign name value var =
VarAssign name (declareVariables value) var
declareVariables Literal i = Literal i
...etc...
findVariablesInBlock VarDecl name = [name]
findVariablesInBlock Block contents _ = []
findVariablesInBlock VarAssign name value _ =
findVariablesInBlock value
...etc...
(所有代码完全未经测试,纯粹用于说明目的。)
但这太可怕了;我最终走了两次树,一次找到Blocks,一次找到VarDecls,而且有很多样板。另外,鉴于Variable 是不可变的,因此首先用一个注释我的所有节点的用途有限——如果不重新重写整个树,我将无法有效地注释Variable。
备选方案 A:我可以让一切都是可变的。现在我有一棵 STRefs 的树,一切都必须存在于 ST monad 中。作为副作用,我的代码有异味。
备选方案 B:不要尝试将所有内容存储在同一个数据结构中。完全独立地存储 StackFrame 和 Variable 结构,并在我遍历树时构建这些结构,而 AST 不受影响。除了这意味着我不能轻易地从VarRef 映射到Variable,这是练习的重点。我可以创建一个Data.Map VarRef Variable 查找表……但这也太可怕了。
解决这类问题有什么好的 Haskell 习惯用法?
【问题讨论】:
-
好吧,您通常会在每次(通常是递归)调用时随身携带环境(变量/值/...被绑定的地方);) - 但是有很多关于这方面的教程
-
我应该补充一点,我并没有尝试实际上编写编译器;这只是我试图解决的问题中最简单的例子。
-
还是一样 - 如果你不想进入 state/reader-monad (但是)你应该把这些依赖项变成参数并传递它们 - FP 101 ;)
-
@Carsten 为什么是“臭名昭著”?
-
@Jubobs 你是对的,这对我不公平 - 对不起(我在考虑这个索赔 - 我从来没有在 48 小时内做到这一点;)
标签: haskell