Haskell - StateT monad中没有输出

时间:2017-01-18 19:11:08

标签: haskell

为了熟悉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没有输出。

所以问题是:如何修复它,最重要的是,为什么不打印任何内容?

编辑:谢谢大家的答案,他们帮助很多。

3 个答案:

答案 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)

最简单的解决方法是使用joinIO (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."