管理深层嵌套括号模式有哪些好的Haskell约定?

时间:2014-10-18 03:43:25

标签: haskell

我目前正在使用Haskell绑定到HDF5 C库。像许多C库一样,这个库在函数调用中使用了很多指针。

分配和释放C资源的常用“最佳实践”Haskell函数遵循bracket pattern,如allocawithArray等。在使用它们时,我经常输入几个嵌套括号。例如,这里是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)

在上面的代码中,嵌套括号是我写的withDatasetwithDataspacealloca3的括号函数,我写这些函数是为了防止括号嵌套深入另外3个级别代码。对于具有大量资源获取调用和指针参数的C库,使用标准括号原语进行编码可能无法管理(这就是我编写alloca3以减少嵌套的原因。)

通常,在需要分配和释放许多资源(例如使用C调用)时,是否有任何最佳实践或编码技术来帮助减少括号的嵌套?我找到的唯一选择是ResourceT转换器,它从教程看起来像是为了使交错资源获取/释放成为可能,而不是简化括号模式。

1 个答案:

答案 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
相关问题