如何在 IO 中包装 monadic 动作
How to wrap monadic action in IO
我正在尝试将 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
monad 似乎有点麻烦——而且感觉不对。
我想要实现的是:
doStuffInGameMonad :: Game a -> Game ()
doStuffInGameMonad gameAction = ioAction $ gameAction
我的直觉告诉我,这应该是可能的,因为我的 Game
monad 知道 IO。有没有办法隐式 convert/unlift Game
monad?
如果我的术语不正确,请原谅。
诀窍是将游戏动作定义为类型 class:
class Monad m => GameMonad m where
spawnCreature :: Position -> m Creature
moveCreature :: Creature -> Direction -> m ()
然后,为 ReaderT State IO
声明一个 GameMonad
的实例 - 使用 ReaderT / IO 操作实现 spawnCreature
和 moveCreature
;是的,这可能意味着 liftIO
's, but only within said instance - the rest of your code will be able to call spawnCreature
and moveCreature
without complications, plus your functions' 类型签名将指示函数具有哪些功能:
spawnTenCreatures :: GameMonad m => m ()
在这里,签名告诉你这个函数 只 执行 GameMonad 操作 - 它不会,比如说,连接到互联网,写入数据库,或启动导弹:)
(事实上,如果您想进一步了解这种风格,google 的技术术语是 "capabilities")
您可以使用的一个抽象是 MonadUnliftIO
class from the unliftio-core
package. You can do it using 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
我正在尝试将 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
monad 似乎有点麻烦——而且感觉不对。
我想要实现的是:
doStuffInGameMonad :: Game a -> Game ()
doStuffInGameMonad gameAction = ioAction $ gameAction
我的直觉告诉我,这应该是可能的,因为我的 Game
monad 知道 IO。有没有办法隐式 convert/unlift Game
monad?
如果我的术语不正确,请原谅。
诀窍是将游戏动作定义为类型 class:
class Monad m => GameMonad m where
spawnCreature :: Position -> m Creature
moveCreature :: Creature -> Direction -> m ()
然后,为 ReaderT State IO
声明一个 GameMonad
的实例 - 使用 ReaderT / IO 操作实现 spawnCreature
和 moveCreature
;是的,这可能意味着 liftIO
's, but only within said instance - the rest of your code will be able to call spawnCreature
and moveCreature
without complications, plus your functions' 类型签名将指示函数具有哪些功能:
spawnTenCreatures :: GameMonad m => m ()
在这里,签名告诉你这个函数 只 执行 GameMonad 操作 - 它不会,比如说,连接到互联网,写入数据库,或启动导弹:)
(事实上,如果您想进一步了解这种风格,google 的技术术语是 "capabilities")
您可以使用的一个抽象是 MonadUnliftIO
class from the unliftio-core
package. You can do it using 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