如何在IO中包装单子动作

时间:2019-06-25 06:24:56

标签: haskell monads monad-transformers reader-monad

我正在尝试将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单子?

如果我的术语不正确,请原谅。

2 个答案:

答案 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操作实现spawnCreaturemoveCreature;是的,这可能暗示着liftIO,但是仅在所述实例内-您的其余代码将能够调用spawnCreaturemoveCreature而不会带来复杂性,以及函数的类型签名将指示该功能具有的功能:

spawnTenCreatures :: GameMonad m => m ()

在这里,签名告诉您该功能会执行GameMonad操作-它不会连接互联网,写入数据库或发射导弹:)

(实际上,如果您想了解有关此样式的更多信息,则Google的技术术语为“功能”)