Context: I need to write basically a stateless compiler that converts VM bytecode to machine codes. Most VM commands can be translated without saving using a clean function, for example:
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 teams need to insert labels, which should be different for each call.
I understand how to do this with the "global" state object for the entire compiler:
compileGt n = [label ++ ":", "cmp ax,bx", "jgt " ++ label] where label = "cmp" ++ show n compile :: Int -> COMPILER_STATE -> VM_COMMAND -> (COMPILER_STATE, [String])
But I think this is bad, because each specialized compilation function needs only a small piece of state or even nothing at all. For example, in not so purely functional JavaScript, I would implement specialized compilation functions with a local state in 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 of why this is a really bad idea. Should there be something like that?
type compileFn = State -> VM_COMMAND -> [String] (compileFn, State) -> VM_COMMAND -> ([String], (compileFn, State))