我试图将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
正在以间隔计划另一个IO操作。每次拆开Game
Monad似乎有些麻烦 - 觉得错误。
我要实现的目标是:
doStuffInGameMonad :: Game a -> Game ()
doStuffInGameMonad gameAction = ioAction $ gameAction
我的直觉告诉我,这应该以某种方式有可能,因为我的Game
Monad知道IO。有没有办法隐式转换/Unlift Game
Monad?
如果我的术语不正确,请原谅。
您可以使用的一个抽象是unliftio-core
软件包中的MonadUnliftIO
类。您可以使用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
诀窍是将游戏动作定义为类型类:
class Monad m => GameMonad m where
spawnCreature :: Position -> m Creature
moveCreature :: Creature -> Direction -> m ()
然后,使用readert/io操作声明ReaderT State IO
的GameMonad
实例 - 实现spawnCreature
和moveCreature
;是的,这可能意味着liftIO
的s,但仅在上述实例中 - 您的其余代码将能够无意义地调用spawnCreature
和moveCreature
,加上您的功能类型签名将指示该函数具有哪些功能:
spawnTenCreatures :: GameMonad m => m ()
在这里,签名告诉您此功能执行GameMonAd操作 - 例如,它不连接到Internet,写入数据库或启动导弹:(
(实际上,如果您想了解有关此样式的更多信息,那么Google的技术术语是"功能"(