0

这是我的代码的简化:

import Database.PostgreSQL.Simple (Connection)
import qualified Streaming.Prelude as S
import Streaming.ByteString.Char8 as C
import Streaming.Zip (gunzip)
import Streaming

main :: IO ()
main = do
  res <- runResourceT $ calculateA myLinesStream
  return ()

type MyLinesStream m r = S.Stream (S.Of String) m r

connect :: IO Connection
connect = undefined

close :: Connection -> IO ()
close = undefined

calculateA :: MonadIO m => MyLinesStream m r -> m ()
calculateA stream = liftIO (bracket connect close (go stream))
  where
    go :: MonadIO m => MyLinesStream m r -> Connection -> m ()
    go stream conn = stream & S.length_ >>= liftIO . print

myLinesStream :: (MonadIO m, MonadResource m) => MyLinesStream m ()
myLinesStream = do
  S.each ["1.zip", "2.zip"]
    & S.mapM (\fileName -> C.readFile fileName & gunzip)
    & S.mconcat
    & C.lines
    & mapsM (S.toList . C.unpack)
    & void

在以下行中存在类型错误go stream

calculateA stream = liftIO (bracket connect close (go stream))

错误说:

Couldn't match type ‘m’ with ‘IO’
  ‘m’ is a rigid type variable bound by
    the type signature for:
      calculateA :: forall (m :: * -> *) r.
                    MonadIO m =>
                    MyLinesStream m r -> m ()
Expected type: Connection -> IO ()
    Actual type: Connection -> m ()

问题

  1. 怎样做才能使这个代码类型检查并且仍然保证在calculateA函数中释放资源是安全的?
  2. 我正在使用读取多个文件C.readFile,然后将其包装在runResourceT. 这会正确释放所有文件句柄吗?
  3. 配乐好不好?(请注意,我需要与calculateA分开的功能myLinesStream
4

1 回答 1

2

问题是你试图使用bracket一个过于笼统的单子。bracket有签名:

bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c   

它将IO动作作为参数。但是,go您传递给的函数bracket需要m在调用者选择的通用基本 monad 中工作calculateA(您稍后将该 monad 设置为ResourceT IOin main)。

基地bracket不要很好地混合。相反,您需要从 resourcet 包中使用特殊函数,例如and ,并使用它们来定义一个帮助器,例如:ResourceTallocaterelease

bracketStream :: (Functor f, MonadResource m) 
              => IO a 
              -> (a -> IO ()) 
              -> (a -> Stream f m b) 
              -> Stream f m b
bracketStream alloc free inside = do
        (key, seed) <- lift (allocate alloc free)
        r <- inside seed
        release key
        pure r

它是如何工作的?如果您有一个Xs 流,它会在流的开头添加一个分配操作(注册在异常终止的情况下要调用的相应清理操作,例如异常),并且它还会在流已用尽:

(分配+注册清理)XXX ... X(清理)

你写了:

我正在使用 C.readFile 读取多个文件,然后将其包装在 runResourceT 中。这会正确释放所有文件句柄吗?

是的。With ResourceT,资源要么在执行明确的清理操作时被释放,要么在我们“退出” ResourceTwith 时runResourceT(可能是异常的,有异常)。

因此,如果我们读取一个 X 流,然后是一个 Y 流,我们将有:

(allocate+register cleanup) X X X ... X (cleanup) (allocate+register cleanup) Y Y Y ... Y (cleanup)

也就是说,产生 Xs 的资源将在分配产生 Ys 的资源之前被释放。

于 2022-02-11T20:16:46.803 回答