haskell MonadIO包含资源

dfuffjeb  于 12个月前  发布在  其他
关注(0)|答案(2)|浏览(223)

我正在实现一个括号类型的模式。假设我有一些资源。为了简单起见,我将使用以下代码

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


有没有可能实现一个名为WithResMonadIO示例,它将为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

a6b3iqyw

a6b3iqyw1#

正如@snak和@willeM_货车Onsem所提到的,我实际上可以使用 State monad。下面是一个快速重写:

import Control.Monad
import Control.Monad.IO.Class
import Data.Bifunctor

newtype WithRes a = WithRes {runWithRes :: Res -> IO (a, Res)}

instance Functor WithRes where
  f `fmap` x = WithRes $ fmap (first f) . runWithRes x

instance Applicative WithRes where
  pure x = WithRes $ return . (x,)
  (<*>) = ap

instance Monad WithRes where
  x >>= f = WithRes $ uncurry runWithRes . first f <=< runWithRes x

instance MonadIO WithRes where
  liftIO x = WithRes $ \r -> (,r) <$> x

字符串
所以useR可以写成下面的形式

useR :: WithRes ()
useR = WithRes $ fmap ((),) . use


现在我们可以在WithRes monad之外执行acquirerelease来设置Res的初始值(如snak推荐的):

main2 :: IO ()
main2 = do
  r <- acquire
  (x, r) <- flip runWithRes r $ do
    useR
    useR
    useR
  release r


或者,在我看来,我们可以做得更好。我们可以把acquire/release变成一元动作。

acquireR :: WithRes ()
acquireR = WithRes $ \r -> ((),) <$> acquire

releaseR :: WithRes ()
releaseR = WithRes $ \r -> release r >> return ((), Res 0)


并添加一个名为evalWithRes的简单runWithRes Package 器,以获得更好的接口

evalWithRes :: Res -> WithRes a -> IO a
evalWithRes r w = fst <$> runWithRes w r


所以我们可以完全从WithRes monad内部运行整个过程。

main3 :: IO ()
main3 = evalWithRes (Res 0) $ do
  acquireR
  useR
  releaseR


注意,我们使用了默认值Res 0。避免它的一种方法是将其 Package 到Maybe中,因此如果它是Nothing,则必须忽略useR操作,但这是另一回事。
另外,我会注意到,useR/acquireR/releaseR使用了相同的wrap-unwrap WithRes值的模式。为了简化编写这样的东西,我们可以添加一些功能,将与状态一起工作,如transformers lib所做的。但对于这个问题,根本没有必要。

dldeef67

dldeef672#

我推荐bracket + ReaderT。像这样:

acquire :: IO Res
release :: Res -> IO ()
useR :: ReaderT Res IO ()
(acquire, release, useR) = undefined

main :: IO ()
main = bracket acquire release (runReaderT useR)

字符串
您可以在useR中调用askasks来访问资源。

相关问题