我正在尝试遵循将状态与 IO 操作结合起来构建 AppState 和 IO monad 中给出的建议。我得到的是这样的:
module Main where
import Control.Monad.State
import Control.Monad.Trans
data ST = ST [Integer] deriving (Show)
type AppState = StateT ST IO
new = ST []
append :: Integer -> State ST ()
append v = state $ \(ST lst) -> ((), ST (lst ++ [v]))
sumST :: State ST Integer
sumST = state $ \(ST lst) -> (sum lst, ST lst)
script = do
append 5
append 10
append 15
sumST
myMain :: AppState ()
myMain = do
liftIO $ putStrLn "myMain start"
let (res, st) = runState script new
liftIO $ putStrLn $ show res
liftIO $ putStrLn "myMain stop"
main = runStateT myMain (ST [15])
这其中的某些部分我没有得到。script
我有andmyMain
和 让我很困扰main
。它还困扰着我,我必须在runState
内部执行,并且我必须在我的主函数中myMain
输入初始状态。runStateT
我想要我的“脚本”,可以这么说,直接在 myMain 函数中,因为 myMain 的全部意义在于能够直接在 myMain 和打印操作旁边运行追加和求和。我想我应该能够做到这一点,而不是:
myMain :: AppState ()
myMain = do
liftIO $ putStrLn "myMain start"
append 5
append 10
append 15
r <- sumST
liftIO $ putStrLn $ show res
liftIO $ putStrLn "myMain stop"
main = runState myMain
我曾认为 monad 转换器的目的是让我可以在函数中执行我的 State monad 操作(如上所示)并将 IO 操作提升到该函数中。设置所有这些以便我可以删除其中一个间接层的正确方法是什么?
除了 Daniel 的解决方案(我已经标记了该解决方案),我还发现了一些变体,它们也可能对这种情况有所帮助。一、myMain和main的最终实现:
myMain :: AppState ()
myMain = do
liftIO $ putStrLn "myMain start"
append 5
append 10
append 15
res <- sumST
liftIO $ putStrLn $ show res
liftIO $ putStrLn "myMain stop"
main = runStateT myMain new
现在,append 和 sumST 的各种实现,除了 Daniel 的:
append :: Integer -> AppState ()
append v = state $ \(ST lst) -> ((), ST (lst ++ [v]))
sumST :: AppState Integer
sumST = state $ \(ST lst) -> (sum lst, ST lst)
和(注意只有类型声明改变;事实上你可以完全省略类型声明!)
append :: MonadState ST m => Integer -> m ()
append v = state $ \(ST lst) -> ((), ST (lst ++ [v]))
sumST :: MonadState ST m => m Integer
sumST = state $ \(ST lst) -> (sum lst, ST lst)
我突然想到 AppState/StateT monad与基本的 State monad 不同,我正在为 State monad 编写 sumST 和 append 代码。从某种意义上说,它们也必须被提升到 StateT monad 中,尽管正确的思考方式是它们必须在 monad 中运行(因此,runState script new
)。
我不确定我是否完全理解它,但我会使用它一段时间,阅读 MonadState 代码,并在它最终在我脑海中运行时写一些关于它的东西。