【发布时间】:2017-03-01 12:15:54
【问题描述】:
上下文:我需要编写一个几乎无状态的编译器,将 VM 字节码转换为机器码。大多数 VM 命令都可以使用如下纯函数进行无状态转换:
compilePop = ["mov ax, @sp", "dec ax", "mov @sp, ax"]
compile :: VM_COMMAND -> [String]
compile STACK_POP = compilePop
-- compile whole program
compileAll :: [VM_COMMAND] -> [String]
compileAll = flatMap compile
但有些命令需要插入标签,每次调用都应该不同。
我了解如何对整个编译器使用“全局”状态对象:
compileGt n = [label ++ ":", "cmp ax,bx", "jgt " ++ label]
where label = "cmp" ++ show n
compile :: Int -> COMPILER_STATE -> VM_COMMAND -> (COMPILER_STATE, [String])
-- here state currently contains only single integer, but it will grow larger
compile lcnt STACK_POP = (lcnt, compilePop)
compile lcnt CMP_GT = (lcnt + 1, compileGt lcnt)
compileAll commands = snd $ foldr compile commands 0
-- incorrect, but you get the idea
但我认为这很糟糕,因为每个专门的编译函数只需要一小部分状态,甚至根本不需要。例如,在非纯函数式 JavaScript 中,我会在闭包中实现具有本地状态的专门编译函数。
// compile/gt.js
var i = 0;
export default const compileGt = () => {
const label = "cmp" + i++;
return [label ++ ":", "cmp ax,bx", "jgt " ++ label];
};
// index.js
import compileGt from './compile/gt';
function compile (cmd) {
switch (cmd) {
case CMP_GT: return compileGt();
// ...
}
}
export default const compileAll = (cmds) => cmds.flatMap(compile);
所以问题是我如何在 Haskell 中做同样的事情,或者解释为什么它真的是个坏主意。应该是这样的吗?
type compileFn = State -> VM_COMMAND -> [String]
(compileFn, State) -> VM_COMMAND -> ([String], (compileFn, State))
【问题讨论】:
-
使用状态单子。
-
@BenjaminHodgson 这很明显,但我不明白它如何回答我关于全球与本地状态的问题。我在问题的最后提出的界面是否正确?特定编译器的状态不同,如何组合?
标签: haskell closures state-monad