在 haskell 中隐藏具有 newtype 的嵌套状态转换器



我不确定我想要完成的事情是否理智(请保持友善(。但是我有一个小游戏的想法,游戏需要有一些状态,并且状态会用一些随机组件更新(否则会有点无聊(。看到 StdGen 也是某种状态,我开始像这样建模我的程序

import Control.Monad.State
import qualified System.Random as R
type Rng = StateT StdGen IO
random :: (Random a) => Rng a
random = state R.random
randoms :: (Random a) => Int -> Rng [a]
randoms n = replicateM n random
type GameS = [Int]-- not important right now
type Game = StateT GameS Rng
mainGame :: Game ()
mainGame = do
    s <- gets
    n <- lift $ randoms 10 :: (Rng [Int]) 
    put s ++ n
mainRng :: Rng ()
mainRng = do
    liftIO $ "Inside Rng!"
    -- here do stuff that has to do with game setup and so on dependent on rng
    runStateT mainGame [1,2,3,4]

main :: IO ()
main = do
    g <- R.newStdGen
    runStateT mainRng g

好吧,成功了!因此,让我们尝试将我们的一些细节隐藏在 newtype 后面。

-- change type aliases for Game and Rng to newtype
newtype Rng a {
   runR :: StateT R.StdGen IO a
} deriving (Applicative, Functor, Monad, MonadIO, MonadState R.StdGen)
newtype Game a {
   runG :: StateT GameS Rng a
} deriving (Applicative, Functor, Monad, MonadIO, MonadState GameS)
-- and create a expose newRun functions
runRng :: Rng a -> IO (a, R.StdGen)
runRng k = do
    g <- R.newStdGen
    runStateT (runR k) g
runGame :: Game a -> Rng (a, GameS)
runGame k = let initial = [1,2,3]
             in runStateT (runG k) initial
-- mainGame as before
mainGame :: Game ()
mainGame = do
   liftIO $ print "Inside game"
   s <- gets
   n <- lift $ randoms 10 :: (Rng [Int])
   put s ++ n
main :: IO ()
main = do
    final <- runRng $ do
        liftIO $ print "Inside rng moand"
        runGame mainGame
    print $ show final

这在一定程度上有效。在mainGame里面,我可以执行liftIO和所有状态操作,除非我尝试lift以获取一些随机数,否则我会收到错误couldn't match type 't0 Rng' with 'Game'

我是否需要以某种方式为我的GameRng类型实现MonadTrans

任何帮助都会很棒!

我想

我想通了。如果我像这样更改mainGame

mainGame :: Game ()
mainGame = do
   liftIO $ print "Inside game"
   s <- gets
   n <- Game . lift $ randoms 10 :: (Rng [Int])
   put s ++ n

它按预期工作。看看MonadTrans电梯的定义,似乎这就是我所缺少的。因此,制作MonadTrans的游戏实例将解决我的问题。

因此,对Game类型进行以下更改:

newtype GameT m a {
    runG :: StateT GameS m a
} deriving (Applicative, Functor, Monad, MonadIO, MonadState, MonadTrans GameS)
type Game = GameT Rng

会让我做我想做的事。我仍然不太确定为什么我必须使用签名GameT m a进行GameT才能制作MonadTrans的实例,在这里挣扎着类型,也许有人可以插话。

最新更新