0

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!

Patrik
  • 255
  • 3
  • 11

1 Answers1

1

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.

Patrik
  • 255
  • 3
  • 11
  • 1
    You clearly understand monads, so monad transformers are not a big jump. A monad transformer is a type function which takes a monad and returns a new monad, typically adding some features such as error handling or state. Since a monad is a type function which takes a type and returns a type, `m :: * -> *`, transformers must have a kind of `t :: (* -> *) -> (* -> *)`, also known as `t :: (* -> *) -> * -> *`. So a monad transformer has two type parameters: a monad and a regular type – Benjamin Hodgson May 10 '17 at 17:12