I'm not sure what I want to accomplish is sane or not (please be nice). But i had an idea for a small game, the game will need to have some state and the state is updated with some random component (otherwise it would be kind of boring). Seeing as the StdGen is also some kind of state i started modelling my program like this
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
Alright that worked! So let's try and hide some of our details behind a 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
This works to a point. Inside mainGame
i can do liftIO and all the state operations except when i try to lift
to get some random numbers i get a error couldn't match type 't0 Rng' with 'Game'
Do i somehow need to implement MonadTrans
for my Game
and Rng
types?
Any help with thid would be great!
I think I figured it out. If i changed mainGame
like so:
mainGame :: Game ()
mainGame = do
liftIO $ print "Inside game"
s <- gets
n <- Game . lift $ randoms 10 :: (Rng [Int])
put s ++ n
It works as expected. And looking at the definition of MonadTrans lift it would seem like thats what im missing. So makeing a Game instance of MonadTrans would solve my problem.
so doing the following changes to the Game
type:
newtype GameT m a {
runG :: StateT GameS m a
} deriving (Applicative, Functor, Monad, MonadIO, MonadState, MonadTrans GameS)
type Game = GameT Rng
will let me do what I want. I'm still no quite sure why i have to make GameT
with the signature GameT m a
to be able to make an instance of MonadTrans
, struggeling with the types here, maybe some one can chimn in on that.