What are some good Haskell conventions for managing deeply nested parenthesis patterns?

I am currently working with Haskell bindings for the HDF5 C library. Like many C libraries, its function calls use a lot of pointers.

Haskell's usual “best practice” function for allocating and allocating C resources follows a bracket , such as alloca , withArray , etc. When using them, I often enter several nested brackets. For example, here is a short excerpt for HDF5 bindings:

 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) 

In the above code, the enclosed brackets are the brackets that I wrote withDataset , withDataspace and alloca3 that I wrote to prevent nesting of brackets from three levels in the code. For C libraries with a lot of resource collection calls and pointer arguments, encoding with standard parenthesis primitives can become unmanageable (which is why I wrote alloca3 to reduce nesting.)

As a rule, are there any best practices or coding methods that help reduce the enclosure of brackets when you need to allocate and release many resources (for example, using C calls)? The only alternative I found is a ResourceT transformer, which from the tutorial looks like it is designed to make access / and not simplify the bracket pattern.

+7
haskell
source share
1 answer

I recently researched this issue in Scala . A repeating pattern (a -> IO r) -> IO r , where a given function is performed in some context of resource allocation, taking into account a value of type a . And this is just ContT r IO a , which is easily available in Haskell. Therefore, we can write:

 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 "..." 

Standard monads / applicative functions allow you to simplify your code, for example:

 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 
+7
source share

All Articles