State monad 是否适用于简单的递归“循环”?

Bob*_*Bob 4 haskell

我正在从“LYAH”学习 Haskell,并学习了 State monad。作为练习,我正在努力实现一个简单的“虚拟CPU”。状态单子似乎很适合这个,但我不知道如何使用它。这是我目前所拥有的一个非常精简的示例(没有状态单子):

data Instruction = Incr | Decr | Halt

data Computer = Computer { program :: [Instruction],
                           acc :: Int,
                           pc :: Int,
                           halted :: Bool
                         }

main = do
  let comp = Computer { program = [Incr, Decr, Incr, Incr, Halt]
                      , acc = 0
                      , pc = 0
                      , halted = False
                      }
  execute comp

execute :: Computer -> IO ()
execute comp = do
  let (output, comp') = step comp
  putStrLn output
  case halted comp' of
    False -> execute comp'
    True -> return ()

step :: Computer -> (String, Computer)
step comp = let inst = program comp !! pc comp
            in case inst of
                 Decr -> let comp' = comp{ acc = acc comp - 1,
                                           pc = pc comp + 1 }
                             output = "Increment:" ++
                                      "   A = " ++ (show $ acc comp') ++
                                      "  PC = " ++  (show $ pc comp')
                         in (output, comp')
                 Incr -> let comp' = comp{ acc = acc comp + 1,
                                           pc = pc comp + 1 }
                             output = "Decrement:" ++
                                      "   A = " ++ (show $ acc comp') ++
                                      "  PC = " ++  (show $ pc comp')
                         in (output, comp')
                 Halt -> let comp' = comp{halted = True}
                             output = "HALT"
                         in (output, comp')
Run Code Online (Sandbox Code Playgroud)

我的step函数看起来像状态单子,但我不知道如何在这里使用它。会适用吗?它会简化这段代码,还是这个例子更好?

lef*_*out 7

绝对地。step可以非常机械地翻译:

\n
import Control.Monad.Trans.State\n\nstep :: State Computer String\nstep = do\n   comp <- get\n   case program comp !! pc comp of\n    Decr -> do \n      let comp\' = comp { acc = acc comp - 1\n                       , pc = pc comp + 1 }\n      put comp\'\n      return $ "Increment:"\n                  ++ "   A = " ++ (show $ acc comp\')\n                  ++ "  PC = " ++  (show $ pc comp\')\n    Incr -> do\n      let comp\' = comp { acc = acc comp + 1\n                       , pc = pc comp + 1 }\n      put comp\'\n      return $ "Decrement:"\n              ++ "   A = " ++ (show $ acc comp\')\n              ++ "  PC = " ++  (show $ pc comp\')\n    Halt -> do\n      put $ comp{halted = True}\n      return "HALT"\n
Run Code Online (Sandbox Code Playgroud)\n

(这些let comp\' = ...仍然有点尴尬。通过使用库中的状态字段修饰符可以使其变得更加命令lens

\n

您可能会注意到,这State实际上只是StateT,我使用的函数都不是专门针对此的。即你可以立即将签名概括为

\n
step :: Monad m => StateT Computer m String\n
Run Code Online (Sandbox Code Playgroud)\n

事实证明这对 很有用execute,因为你可以将状态修改与IO副作用 \xe2\x80\x93 散布在一起,这正是变压器的用途。

\n
import Control.Monad.IO.Class\n\nexecute :: StateT Computer IO ()\nexecute = do\n  output <- step\n  liftIO $ putStrLn output\n  comp <- get\n  case halted comp of\n    False -> execute\n    True -> return ()\n
Run Code Online (Sandbox Code Playgroud)\n

最后,在main你只需要

\n
main = do\n  let comp = ...\n  evalStatT execute comp\n
Run Code Online (Sandbox Code Playgroud)\n