我正在尝试将ReaderT X IO
monad视为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
会间隔安排另一个IO操作。每次解开Game
单子似乎有点麻烦-感觉很不对。
我想要实现的是:
doStuffInGameMonad :: Game a -> Game ()
doStuffInGameMonad gameAction = ioAction $ gameAction
我的直觉告诉我,这应该可以实现,因为我的Game
monad知道IO。有没有一种方法可以隐式转换/解除Game
单子?
如果我的术语不正确,请原谅。
答案 0 :(得分:3)
您可以使用的一种抽象是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
答案 1 :(得分:2)
技巧是将游戏动作定义为类型类:
class Monad m => GameMonad m where
spawnCreature :: Position -> m Creature
moveCreature :: Creature -> Direction -> m ()
然后,为GameMonad
声明一个ReaderT State IO
的实例-使用ReaderT / IO操作实现spawnCreature
和moveCreature
;是的,这可能暗示着liftIO
,但是仅在所述实例内-您的其余代码将能够调用spawnCreature
和moveCreature
而不会带来复杂性,以及函数的类型签名将指示该功能具有的功能:
spawnTenCreatures :: GameMonad m => m ()
在这里,签名告诉您该功能仅会执行GameMonad操作-它不会连接互联网,写入数据库或发射导弹:)>
(实际上,如果您想了解有关此样式的更多信息,则Google的技术术语为“功能”)