State monad 是否适用于简单的递归 "loop"?

Is the State monad applicable to a simple recursive "loop"?

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

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')

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

当然可以。 step可以翻译的很机械:

import Control.Monad.Trans.State

step :: State Computer String
step = do
   comp <- get
   case program comp !! pc comp of
    Decr -> do 
      let comp' = comp { acc = acc comp - 1
                       , pc = pc comp + 1 }
      put comp'
      return $ "Increment:"
                  ++ "   A = " ++ (show $ acc comp')
                  ++ "  PC = " ++  (show $ pc comp')
    Incr -> do
      let comp' = comp { acc = acc comp + 1
                       , pc = pc comp + 1 }
      put comp'
      return $ "Decrement:"
              ++ "   A = " ++ (show $ acc comp')
              ++ "  PC = " ++  (show $ pc comp')
    Halt -> do
      put $ comp{halted = True}
      return "HALT"

(这些let comp' = ...还是有点笨拙。可以通过使用the lens library中的状态field-modifiers使其变得更imperative-like。)

你可能会注意到 State is actually just a specialization of the StateT transformer,而我使用的 none 函数是专门针对此的。 IE。您可以立即将签名概括为

step :: Monad m => StateT Computer m String

事实证明,这对 execute 派上了用场,因为你在状态修改中穿插了 IO 副作用——这正是转换器的用途。

import Control.Monad.IO.Class

execute :: StateT Computer IO ()
execute = do
  output <- step
  liftIO $ putStrLn output
  comp <- get
  case halted comp of
    False -> execute
    True -> return ()

最后,在main你只需要

main = do
  let comp = ...
  evalStatT execute comp