为了熟悉monad变换器,我编写了以下代码:
data GlobalState = GlobalState {
rng :: StdGen
}
foo :: IO ()
foo =
evalStateT ( StateT $
\s ->
let (v, newrng) = roll $ rng s in
return (putStrLn $ show v,
s { rng = newrng })
) zeroState >>
putStrLn "See you soon."
zeroState :: GlobalState
zeroState = GlobalState {
rng = mkStdGen 0
}
roll :: StdGen -> (Int, StdGen)
roll gen = randomR (1, 6) gen
想法是初始化状态,将其用于IO操作,然后返回到普通IO。然而,有些东西出了问题,只有“很快见到你”被打印出来,putStrLn $ show v
没有输出。
所以问题是:如何修复它,最重要的是,为什么不打印任何内容?
编辑:谢谢大家的答案,他们帮助很多。
答案 0 :(得分:3)
您的问题是,您似乎错误地认为StateT
(m a, s) -> StateT s m a
的类型,实际上它是m (a, s) -> StateT s m a
。那就是m
在外面而不在元组内部。这就是为什么你必须使用return
来返回IO (a,GlobalState)
类型的东西。你需要做的是putStrLn (show v) >> return ((),s {rng = newrng})
。像这样:
import Control.Monad.Trans.State
import System.Random
data GlobalState = GlobalState {
rng :: StdGen
}
foo :: IO ()
foo = evalStateT (StateT $
\s -> let (v, newrng) = roll $ rng s in
putStrLn (show v) >>
return ((),s { rng = newrng })
) zeroState >>
putStrLn "See you soon."
zeroState :: GlobalState
zeroState = GlobalState {
rng = mkStdGen 0
}
roll :: StdGen -> (Int, StdGen)
roll gen = randomR (1, 6) gen
答案 1 :(得分:3)
在我看来,如果使用mtl类型类,monad变换器更容易使用。这也会导致更通用的代码,因为它不会将您与任何具体的数据类型联系起来。另外,我建议通常分开"做什么"部分来自" run"部分。我已经重写了你的代码,希望能够证明我的意思:
import System.Random
import Control.Monad.State
data GlobalState = GS { rng :: StdGen }
zeroState = GS { rng = mkStdGen 0 }
roll :: StdGen -> (Int,StdGen)
roll = randomR (1,6)
-- works with any concrete monad m that supplies IO and GlobalState state
foo :: (MonadIO m, MonadState GlobalState m) => m ()
foo = do
(v,gen) <- gets (roll . rng)
liftIO $ print v
put $ GS gen
-- commit to StateT here
evalFoo :: IO ()
evalFoo = evalStateT foo zeroState
好处是foo
现在可以重复使用。例如,如果我们想多次滚动:
evalFooN :: Int -> IO ()
evalFooN n = evalStateT (replicateM_ n foo) zeroState
答案 2 :(得分:1)
最简单的解决方法是使用join
将IO (IO ())
返回的evalStateT
值减少为IO ()
值。
foo :: IO ()
foo =
(join (evalStateT ( StateT $
\s ->
let (v, newrng) = roll $ rng s in
return (putStrLn $ show v,
s { rng = newrng })
) zeroState) >>
putStrLn "See you soon."
更复杂但更清晰的修复是确保您不在状态本身中引入新的IO
操作,而只是包含一个您自己包装的字符串。
foo :: IO ()
foo =
evalStateT ( StateT $
\s ->
let (v, newrng) = roll $ rng s in
return (show v, s { rng = newrng })
) zeroState >>= putStrLn >>
putStrLn "See you soon."