Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are good Haskell conventions for managing deeply nested bracket patterns?

Tags:

haskell

I am currently working with Haskell bindings to a HDF5 C library. Like many C libraries, this one uses many pointers in its functions calls.

The usual "best practice" Haskell functions for allocating and releasing C resources follow the bracket pattern, like alloca, withArray, etc. In using them, I often enter several nested brackets. For instance, here is a small 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 code above, the nested brackets are bracket functions I wrote withDataset, withDataspace, and alloca3, which I wrote to prevent the bracket nesting from going another 3 levels deep in the code. For C libraries with lots of resource acquisition calls and pointer arguments, coding with the standard bracket primitives can get unmanageable (which is why I wrote alloca3 to reduce the nesting.)

So generally, are there any best practices or coding techniques to help reduce the nesting of brackets when needing to allocate and deallocate many resources (such as with C calls)? The only alternative I have found is the ResourceT transformer, which from the tutorial looks like it is designed to make interleaving resource acquire/release possible, and not to simplify the bracket pattern.

like image 333
fluffynukeit Avatar asked Oct 18 '14 03:10

fluffynukeit


1 Answers

Recently I was investigating this problem in Scala. The recurring pattern is (a -> IO r) -> IO r, where a given function is executed within some resource allocation context given a value of type a. And this is just ContT r IO a, which is readily available in Haskell. So 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 "..."

The standard monad/applicative functions allow you to simplify a lot of 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
like image 128
Petr Avatar answered Oct 05 '22 15:10

Petr