Скрытие вложенных преобразователей состояния с помощью newtype в haskell

Я не уверен, чего я хочу добиться, разумно или нет (пожалуйста, будьте любезны). Но у меня была идея для небольшой игры, в игре должно быть какое-то состояние, и состояние обновляется каким-то случайным компонентом (иначе было бы скучно). Поскольку 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

Хорошо, это сработало! Итак, давайте попробуем скрыть некоторые наши детали за новым типом.

-- 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'

Нужно ли мне как-то реализовать MonadTrans для моих типов Game и Rng?

Любая помощь с этим будет здорово!


person Patrik    schedule 10.05.2017    source источник


Ответы (1)


Думаю, я понял это. Если бы я изменил 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 с сигнатурой GameT m a, чтобы иметь возможность создать экземпляр MonadTrans, здесь я борюсь с типами, может быть, кто-то может вмешаться в это.

person Patrik    schedule 10.05.2017
comment
Вы хорошо понимаете монады, так что трансформеры монад — это не большой скачок. Преобразователь монады — это функция типа, которая принимает монаду и возвращает новую монаду, обычно добавляя некоторые функции, такие как обработка ошибок или состояние. Поскольку монада — это функция типа, которая принимает тип и возвращает тип m :: * -> *, преобразователи должны иметь разновидность t :: (* -> *) -> (* -> *), также известную как t :: (* -> *) -> * -> *. Таким образом, монадный преобразователь имеет два параметра типа: монада и обычный тип. - person Benjamin Hodgson♦; 10.05.2017