Search code examples
haskellffi

How to pass ragged arrays with Haskell FFI


I have a C function that looks something like this:

/**
 * @n       the outer dimension of data
 * @lengths the length of each "row" of data
 * @data    the data itself
 */
double foo(int n, int* lengths, double** data);

I would like to call this function from Haskell, but am not sure how best to handle allocating the inner arrays of the data pointer. In essence, I have something like this:

foreign import ccall "foo.h foo"
    c_foo :: CInt -> Ptr CInt -> Ptr (Ptr CDouble) -> CDouble

haskFoo :: Int -> [Int] -> [[Double]] -> Double
haskFoo :: n lengths data = unsafePerformIO $
  allocaArray n $ \lengths_ptr -> do
    pokeArray lengths_ptr (map fromIntegral lengths)
    allocaArray n $ \data_outer_ptrs -> do
      -- TODO alloc inner arrays
      x <- c_foo (fromIntegral n) lengths_ptr data_outer_ptrs
      return realToFrac x

Ideally I would use something alike allocaArray or withStorableArray (as in the answer here), but I don't know how to do so with a runtime number of nested lambdas. I think I can accomplish what I want with mallocArray but would prefer to use something in the style of alloca that ensures cleanup of the allocated memory.


Solution

  • You don't need to allocate each array separately, just allocate one array with length sum lengths, and then the i'th individual pointer is plusPtr arrayPtr (sum (take i lengths)).

    If for some reason you must allocate these discontiguously, just write your allocation recursively (pseudocode, may contain typos):

    withRagged lengths ds h = go lengths ds [] where
      go (n:lengths) (d:ds) rptrs = alloca n \d_ptr -> go lengths ds (d_ptr:rptrs)
      go [] [] rptrs = h (reverse rptrs)
    
    hasFoo lengths ds = withArray (map fromIntegral lengths) \lengths_ptr ->
      withRagged lengths data \data_ptrs ->
        -- ...
    

    The n parameter seems superfluous and probably shouldn't be part of the Haskell API (nor should, for that matter, be the list of lengths). If you want to avoid the more expensive length, just use Vector instead of [].