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
我正在向“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