What is the right way to imitate state closure in Haskell

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]) -- 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 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)) 
+6
source share
1 answer

If you have...

 data Big = Big { little :: Little, stuff :: Whatever } 

... you can define your own ...

 littleProcessor :: State Little [String] 

... and then use a function like this ...

 innerState :: Monad m => (s -> i) -> (i -> s -> s) -> StateT ima -> StateT sma innerState getI setI (StateT m) = StateT $ \s -> do (a, i) <- m (getI s) return (a, setI is) 

... to raise it to a larger state:

 bigProcessor :: State Big [String] bigProcessor = innerState little (\lb -> b {little = l}) littleProcessor 

(Add additional definitions to taste.)

Using a getter / setter pair in innerState makes it look like it should be possible to tell it in terms of lenses. Indeed, the zoom from the lens is basically innerState with a minimal template:

 {-# LANGUAGE TemplateHaskell #-} import Control.Lens data Big = Big { _little :: Little, _stuff :: Whatever } makeLenses ''Big -- little is now a lens. 
 bigProcessor :: State Big [String] bigProcessor = zoom little littleProcessor 
+8
source

Source: https://habr.com/ru/post/1015367/


All Articles