我目前正在使用Haskell绑定到HDF5 C库。像许多C库一样,这个库在函数调用中使用了很多指针。
分配和释放C资源的常用“最佳实践”Haskell函数遵循bracket pattern,如alloca
,withArray
等。在使用它们时,我经常输入几个嵌套括号。例如,这里是HDF5绑定的一小部分:
selectHyperslab rID dName = withDataset rID dName $ \dID -> do
v <- withDataspace 10 $ \dstDS -> do
srcDS <- c'H5Dget_space dID
dat <- alloca3 (0, 1, 10) $ \(start, stride, count) -> do
err <- c'H5Sselect_hyperslab srcDS c'H5S_SELECT_SET start stride count nullPtr
-- do some work ...
return value
alloca3 (a, b, c) action =
alloca $ \aP -> do
poke aP a
alloca $ \bP -> do
poke bP b
alloca $ \cP -> do
poke cP c
action (aP, bP, cP)
在上面的代码中,嵌套括号是我写的withDataset
,withDataspace
和alloca3
的括号函数,我写这些函数是为了防止括号嵌套深入另外3个级别代码。对于具有大量资源获取调用和指针参数的C库,使用标准括号原语进行编码可能无法管理(这就是我编写alloca3
以减少嵌套的原因。)
通常,在需要分配和释放许多资源(例如使用C调用)时,是否有任何最佳实践或编码技术来帮助减少括号的嵌套?我找到的唯一选择是ResourceT转换器,它从教程看起来像是为了使交错资源获取/释放成为可能,而不是简化括号模式。
答案 0 :(得分:7)
最近我investigating this problem in Scala。循环模式为(a -> IO r) -> IO r
,其中给定函数在给定类型a
的值的某个资源分配上下文中执行。这只是ContT r IO a
,在Haskell中很容易获得。所以我们可以写:
import Control.Monad
import Control.Monad.Cont
import Control.Monad.IO.Class
import Control.Exception (bracket)
import Foreign.Ptr (Ptr)
import Foreign.Storable (Storable)
import Foreign.Marshal.Alloc (alloca)
allocaC :: Storable a => ContT r IO (Ptr a)
allocaC = ContT alloca
bracketC :: IO a -> (a -> IO b) -> ContT r IO a
bracketC start end = ContT (bracket start end)
bracketC_ :: IO a -> IO b -> ContT r IO a
bracketC_ start end = ContT (bracket start (const end))
-- ...etc...
-- | Example:
main :: IO ()
main = flip runContT return $ do
bracketC_ (putStrLn "begin1") (putStrLn "end1")
bracketC_ (putStrLn "begin2") (putStrLn "end2")
liftIO $ putStrLn "..."
标准monad / applicative函数允许您简化许多代码,例如:
allocAndPoke :: (Storable a) => a -> ContT r IO (Ptr a)
allocAndPoke x = allocaC >>= \ptr -> liftIO (poke ptr x) >> return ptr
-- With the monad alloca3 won't be probably needed, just as an example:
alloca3C (a, b, c) =
(,,) <$> allocAndPoke a <*> allocAndPoke b <*> allocAndPoke c
allocaManyC :: (Storable a) => [a] -> ContT r IO [Ptr a]
allocaManyC = mapM allocAndPoke