Context: I need to write a mostly stateless compiler which transforms VM bytecode into machine codes. Most VM commands can be translated statelessly with pure function like the following:
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
But some commands need inserting labels which should be different for every call.
I understand how to do this with a state object "global" for entire compiler:
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
But I think this is bad because every specialized compile function needs only little piece of a state or even none at all. For example in not such a purely functional JavaScript I'd implement specialized compile functions with local state in a closure.
// 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);
So the question is how can I do the same in Haskell or an explanation why it's really bad idea. Should it be something like that?
type compileFn = State -> VM_COMMAND -> [String]
(compileFn, State) -> VM_COMMAND -> ([String], (compileFn, State))