什么是在Haskell中模拟有状态闭包的正确方法

kir*_*oid 6 closures haskell state-monad

上下文:我需要编写一个主要是无状态的编译器,它将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
Run Code Online (Sandbox Code Playgroud)

但是有些命令需要插入标签,每次调用都应该是不同的.

我理解如何使用整个编译器的状态对象"global"来执行此操作:

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
Run Code Online (Sandbox Code Playgroud)

但我认为这很糟糕,因为每个专门的编译函数只需要一小部分状态,甚至根本不需要.例如,在这种纯函数式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);
Run Code Online (Sandbox Code Playgroud)

所以问题是我如何才能在Haskell中做同样的事情或解释为什么它真的很糟糕.它应该是那样的吗?

type compileFn = State -> VM_COMMAND -> [String]
(compileFn, State) -> VM_COMMAND -> ([String], (compileFn, State))
Run Code Online (Sandbox Code Playgroud)

dup*_*ode 8

如果你有...

data Big = Big { little :: Little, stuff :: Whatever }
Run Code Online (Sandbox Code Playgroud)

......你可以定义你的......

littleProcessor :: State Little [String]
Run Code Online (Sandbox Code Playgroud)

...然后使用像这样的功能......

innerState :: Monad m 
    => (s -> i) -> (i -> s -> s) -> StateT i m a -> StateT s m a
innerState getI setI (StateT m) = StateT $ \s -> do
    (a, i) <- m (getI s)
    return (a, setI i s)
Run Code Online (Sandbox Code Playgroud)

......将其提升到更大的状态:

bigProcessor :: State Big [String]
bigProcessor = innerState little (\l b -> b {little = l}) littleProcessor
Run Code Online (Sandbox Code Playgroud)

(添加辅助定义以品尝.)

使用getter/setter对innerState使它看起来应该可以根据镜头来表达它.事实上,zoom镜头基本上innerState是最小化的样板:

{-# LANGUAGE TemplateHaskell #-}
import Control.Lens

data Big = Big { _little :: Little, _stuff :: Whatever }
makeLenses ''Big -- little is now a lens.
Run Code Online (Sandbox Code Playgroud)
bigProcessor :: State Big [String]
bigProcessor = zoom little littleProcessor
Run Code Online (Sandbox Code Playgroud)