我正在尝试按照Combine state with IO actions中给出的建议来建立一个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
和myMain
和 main
,这让我很烦恼。令我困扰的是,我必须在runState
内执行myMain
,并且我必须在我的主函数中将初始状态提供给runStateT
。我想要直接在myMain函数中使用我的“脚本”,因为myMain的整个点是能够直接在myMain中运行append和sum,并且紧跟打印操作。我想我应该能够做到这一点,而不是:
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变换器的重点在于我可以在函数中执行状态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的各种实现:
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相同,而且我正在编码sumST并为State monad添加。从某种意义上说,他们也必须被提升到StateT monad,尽管正确的思考方式是他们必须在monad中运行(因此,runState script new
)。
我不确定我是否完全得到它,但我会使用它一段时间,阅读MonadState代码,并在它最终在我脑海中起作用时写下一些内容。
答案 0 :(得分:10)
问题是你的append
和sumST
函数太单态了!您应该使用更多态state
和get
函数,而不是直接使用put
函数,这样您就可以为它们提供更激动人心的类型
append :: MonadState ST m => Integer -> m ()
append v = do
ST lst <- get
put (ST (lst ++ [v]))
sumST :: MonadState ST m => m Integer
sumST = do
ST lst <- get
return (sum lst)
然后你可以准确写出你提出的myMain
(尽管你仍然需要在main
中给出一个初始状态。)
作为一种风格的东西,我建议不要定义一个新的ST
类型:有许多函数可以使用列表进行处理,并且通过强加ST
构造函数使它们无法使用你和名单之间可能很烦人!如果您使用[Integer]
作为州类型,则可以进行如下定义:
prepend :: MonadState [Integer] m => Integer -> m ()
prepend = modify . (:)
sumST :: MonadState [Integer] m => m Integer
sumST = gets sum
看起来很漂亮,不是吗? =)