能否实现带MonadIO的WithRes类型,简化资源操作使main2等价于main1?
实现持有资源的WithRes MonadIO实例,等价于bracket模式?
我正在实现一种类似bracket的模式。假设我有一个资源,为简化说明,定义如下:
data Res = Res Int deriving(Show) acquire :: IO Res acquire = do print "Acquire:" Res . read <$> getLine release :: Res -> IO () release r = do print "Result:" print r use :: Res -> IO Res use r = do print r let Res x = r in return . Res $ x + 1
对应的常规实现代码是:
main1 :: IO () main1 = do r <- acquire r <- use r release r
现在想实现一个名为WithRes的MonadIO实例,让它持有use函数的参数,从而简化use的形式,定义出以下函数:
useR :: WithRes () useR = ... acquireR :: WithRes () acquireR = ... releaseR :: WithRes () releaseR = ... runWithRes :: WithRes a -> IO a main2 :: IO () main2 = runWithRes $ do acquireR useR releaseR
要求main1和main2的行为完全等价,请问这是否可行?
当然可以实现。核心思路是让WithRes这个monad内部维护资源的状态,借助MonadIO执行IO操作,同时在合适时机更新内部资源。
具体实现
1. 定义WithRes类型
我们可以用StateT包裹IO来实现,既可以维护资源状态,又能支持IO操作:
import Control.Monad.State import Control.Monad.IO.Class newtype WithRes a = WithRes { unWithRes :: StateT (Maybe Res) IO a } deriving (Functor, Applicative, Monad, MonadIO)
这里用Maybe Res是因为资源在acquireR执行前不存在,执行后才会有有效值。
2. 实现acquireR
acquireR负责获取资源,并将其存入WithRes的状态中:
acquireR :: WithRes () acquireR = do res <- liftIO acquire put (Just res)
3. 实现useR
useR从状态中取出资源,执行原use函数,再更新状态:
useR :: WithRes () useR = do Just res <- get newRes <- liftIO $ use res put (Just newRes)
4. 实现releaseR
releaseR从状态中取出资源,执行释放操作:
releaseR :: WithRes () releaseR = do Just res <- get liftIO $ release res
5. 实现runWithRes
runWithRes初始化状态(初始为Nothing),并运行整个WithRes流程:
runWithRes :: WithRes a -> IO a runWithRes = evalStateT (unWithRes) Nothing
6. 验证等价性
此时main2的行为和main1完全一致:
main2 :: IO () main2 = runWithRes $ do acquireR useR releaseR
补充说明
如果需要更严谨的错误处理(比如避免未获取资源就执行useR或releaseR的情况),可以在get操作时加入错误处理逻辑,比如抛出异常或返回Maybe类型结果,但根据原问题需求,当前实现已满足行为等价的要求。
内容的提问来源于stack exchange,提问作者Andrey
相关产品推荐
相关产品推荐

