Как обернуть монадическое действие в IO - PullRequest
3 голосов
/ 25 июня 2019

Я пытаюсь обработать монаду ReaderT X IO как IO для достижения следующего:

-- this is the monad I defined:
type Game = ReaderT State IO                                                                                                            

runGame :: State -> Game a -> IO a                                                                                                      
runGame state a = runReaderT a state                                                                                                    

readState :: Game State                                                                                                                 
readState = ask                                                                                                                         

-- some IO action, i.e. scheduling, looping, etc.                                                                                                                    
ioAction :: IO a -> IO ()
ioAction = undefined

-- this works as expected, but is rather ugly                                                                                                                                       
doStuffInGameMonad :: Game a -> Game ()                                                                                                 
doStuffInGameMonad gameAction = do                                                                                                      
  state <- readState                                                                                                               
  liftIO $ ioAction $ runGame state gameAction

ioAction например, планирует другое действие ввода-вывода с интервалами.Развертывание Game монады каждый раз кажется немного громоздким - и кажется неправильным.

Вместо этого я пытаюсь достичь:

doStuffInGameMonad :: Game a -> Game ()                                                                                                 
doStuffInGameMonad gameAction = ioAction $ gameAction                                                                                   

Моя интуиция подсказывает мне, что это должно бытькак-то возможно, потому что моя Game монада знает о IO.Есть ли способ неявного преобразования / отмены монады Game?

Пожалуйста, извините, если моя терминология неверна.

Ответы [ 2 ]

3 голосов
/ 26 июня 2019

Одна абстракция, которую вы можете использовать, это класс MonadUnliftIO из пакета unliftio-core. Вы можете сделать это, используя withRunInIO.

import Control.Monad.IO.Unlift (MonadUnliftIO(..))

doStuffInGameMonad :: MonadUnliftIO m => m a -> m ()
doStuffInGameMonad gameAction = withRunInIO (\run -> ioAction (run gameAction))

Другим менее полиморфным решением было бы использовать mapReaderT.

doStuffInGameMonad :: Game a -> Game ()
doStuffInGameMonad gameAction = mapReaderT ioAction gameAction
2 голосов
/ 25 июня 2019

Хитрость в том, чтобы определить игровые действия как класс типов:

class Monad m => GameMonad m where
  spawnCreature :: Position -> m Creature
  moveCreature :: Creature -> Direction -> m ()

Затем объявите экземпляр GameMonad для ReaderT State IO - реализация spawnCreature и moveCreature с использованием действий ReaderT / IO; да, это, скорее всего, будет означать liftIO, но только в пределах указанного экземпляра - остальная часть вашего кода сможет без проблем вызывать spawnCreature и moveCreature, плюс сигнатуры типа ваших функций будут указывать, какие возможности функции имеет:

spawnTenCreatures :: GameMonad m => m ()

Здесь подпись говорит вам, что эта функция только выполняет операции GameMonad - что она, скажем, не подключается к Интернету, не пишет в базу данных и не запускает ракеты:)

(На самом деле, если вы хотите узнать больше об этом стиле, технический термин для Google - это «возможности»)

...