hasquant-0.8.0.0: QuantLib/Internal.hs
module QuantLib.Internal
(
Error(..)
, Day -- reexport for simplicity
, minDate
, maxDate
-- marshalling helpers
, prePtr
, preErrorCheck
, errorCheck
, fromMaybeBool
, toMaybeBool
, peekDynString
, peekEnum
, peekDouble
, preEnum
, withComplex
, preComplex
, peekComplex
, preNum
, preArray
, preArrayWith
, preIntArray
, preDoubleArray
, preCStringArray
, prePtrArray
, withEnumArray
, withIntArray
, withBoolArray
, withDoubleArray
, withRealVector
, withRealVectorRaw
, withNonEmptyDoubleArray
, withDoubleArrayRaw
, withBoolArrayRaw
, withDayPtr
, withStringArray
, peekCStringArray
, fromEnumQuantity
, toEnumQuantity
, fromEnumDouble
, toEnumDouble
, fromEnumC
, toEnumC
, withDay
, toDay
, withMaybeDay
, fromMaybeInt
, toMaybeDay
, peekDayArray
, peekBoolArray
, withDayArray
, peekDoubleArray
, peekRealVector
, borrowRealVector
, RealVector
, NonEmptyVector
, singletonNonEmptyVector
, consNonEmptyVector
, nonEmptyVector
, nonEmptyVectorToVector
, withNonEmptyRealVector
, toSerial
, fromSerial
, qlSavedSettings
, qlFreeSavedSettings
, fromMaybeDouble
, fromMaybeEnum
, fromMaybeEnumQuantity
, peekIntArray
, peekIntArray'
, peekUIntArray
, peekWord
, peekStructArray
, peekPtrArray
, Matrix(..)
, RealMatrix(..)
, boxedRealMatrix
, realMatrixFromVector
, objectMatrix
, qlNullInteger
, qlFreeAdditionalResults
, uncurryNested
, zipWith6
)
where
import Data.Complex(Complex((:+)))
import Foreign.C.Types(CUInt(..), CInt(..), CDouble(..))
import Foreign.C.String(CString, peekCString, withCString)
import Foreign.Ptr(Ptr, nullPtr, castPtr)
import Foreign.ForeignPtr(FinalizerPtr, newForeignPtr, newForeignPtr_, castForeignPtr)
import Foreign.Marshal.Array(peekArray, withArray)
import Foreign.Marshal.Utils(with, toBool, fromBool, withMany)
import Foreign.Storable(peek, poke, peekElemOff, pokeElemOff, Storable)
import Foreign.Marshal.Alloc(alloca)
import Control.Exception(Exception, SomeException, throwIO, mask, mask_, finally, onException, catch)
import Foreign.StablePtr(StablePtr, castStablePtrToPtr, deRefStablePtr, freeStablePtr)
import Control.Monad(when, (>=>))
import Data.Time.Calendar(Day(ModifiedJulianDay), toModifiedJulianDay, fromGregorian)
import Data.List.NonEmpty(NonEmpty, toList)
import Data.Vector.Storable(Vector, unsafeFromForeignPtr0)
import qualified Data.Vector.Storable as V
data Error = CPlusPlusException String
-- |Call name, minimum required version, and linked QuantLib version.
| UnsupportedQuantLibVersion String String String
| DateConversion Day
| EnumConversion String
-- |A Haskell callback returned an array of the wrong length: expected, then returned.
| CallbackResultLength Int Int
deriving (Show, Eq)
instance Exception Error
foreign import ccall safe "ql.h qlErrorMessage" qlErrorMessage :: Ptr () -> IO CString
foreign import ccall safe "ql.h qlErrorCall" qlErrorCall :: Ptr () -> IO CString
foreign import ccall safe "ql.h qlErrorRequiredVersion" qlErrorRequiredVersion :: Ptr () -> IO CString
foreign import ccall safe "ql.h qlErrorLinkedVersion" qlErrorLinkedVersion :: Ptr () -> IO CString
foreign import ccall safe "ql.h qlTakeErrorException" qlTakeErrorException :: Ptr () -> IO (StablePtr SomeException)
foreign import ccall safe "ql.h qlFreeError" qlFreeError :: Ptr () -> IO ()
errorCheck :: Ptr (Ptr a) -> IO ()
errorCheck p = mask_ $ do
errorPtr <- peek p
when (errorPtr /= nullPtr) $ do
poke p nullPtr
let owned = castPtr errorPtr
flip finally (qlFreeError owned) $ do
saved <- qlTakeErrorException owned
if castStablePtrToPtr saved == nullPtr
then do
call <- qlErrorCall owned >>= peekCString
if null call
then qlErrorMessage owned >>= peekCString >>= throwIO . CPlusPlusException
else do
required <- qlErrorRequiredVersion owned >>= peekCString
linked <- qlErrorLinkedVersion owned >>= peekCString
throwIO $ UnsupportedQuantLibVersion call required linked
else (deRefStablePtr saved `finally` freeStablePtr saved) >>= throwIO
-- Keep the native error, including an unconsumed callback exception, scoped to adoption.
preErrorCheck :: (Ptr (Ptr a) -> IO b) -> IO b
preErrorCheck f = mask_ $ with nullPtr $ \p ->
(do
result <- f p `catch` \ex -> errorCheck p >> throwIO (ex :: SomeException)
errorCheck p
pure result) `finally` (do
pending <- peek p
when (pending /= nullPtr) (qlFreeError (castPtr pending)))
fromMaybeBool :: Maybe Bool -> CInt
fromMaybeBool = maybe (-1) fromBool
foreign import ccall safe "ql.h qlNullInteger" qlNullInteger :: CInt
foreign import ccall safe "ql.h qlNullReal" qlNullReal :: CDouble
fromMaybeInt :: (Integral a, Integral b) => Maybe a -> b
fromMaybeInt = maybe (fromIntegral qlNullInteger) fromIntegral
fromMaybeDouble :: Maybe Double -> CDouble
fromMaybeDouble = maybe qlNullReal realToFrac
-- |Marshals a Maybe of a plain by-value C++ enum whose lowest member maps to 0
-- (true of every by-value enum bound so far) as a C int, using -1 as the
-- ext::nullopt sentinel -- mirrors fromMaybeBool's convention.
fromMaybeEnum :: Enum a => Maybe a -> CInt
fromMaybeEnum = maybe (-1) (fromIntegral . fromEnum)
-- |'Nothing' emits the enum's -1 sentinel (same convention as 'fromMaybeEnum'), paired with a
-- placeholder quantity of 0 -- for enums like 'TimeUnit' that start at 0 and can't self-sentinel.
fromMaybeEnumQuantity :: Enum a => Maybe (Word, a) -> (CInt, CInt)
fromMaybeEnumQuantity = maybe (0, -1) fromEnumQuantity
toMaybeBool :: CInt -> Maybe Bool
toMaybeBool x = if x == -1 then Nothing else Just $ toBool x
peekDynString :: CString -> IO String
peekDynString x
| x == nullPtr = pure "" -- The following errorCheck reports a failed C++ call.
| otherwise = peekCString x `finally` qlFreeString x
peekEnum :: (Enum a) => Ptr CInt -> IO a
peekEnum x = toEnum . fromIntegral <$> peek x
peekDouble :: Ptr CDouble -> IO Double
peekDouble x = realToFrac <$> peek x
peekWord :: Ptr CUInt -> IO Word
peekWord x = fromIntegral <$> peek x
-- initialize pointer to a enum with a valid value before passing it to the function
preEnum :: (Storable a, Bounded a) => (Ptr a -> IO b) -> IO b
preEnum = with minBound
preNum :: (Storable a, Num a) => (Ptr a -> IO b) -> IO b
preNum = with 0
foreign import ccall safe "ql.h qlFreeString" qlFreeString :: CString -> IO ()
foreign import ccall safe "ql.h qlFreeInts" qlFreeInts :: Ptr CInt -> IO ()
foreign import ccall safe "ql.h qlFreeUInts" qlFreeUInts :: Ptr CUInt -> IO ()
foreign import ccall safe "ql.h qlFreeDoubles" qlFreeDoubles :: Ptr CDouble -> IO ()
foreign import ccall safe "ql.h &qlFreeDoubles" qlFreeDoublesFin :: FinalizerPtr CDouble
foreign import ccall safe "ql.h qlFreePointerArray" qlFreePointerArray :: Ptr (Ptr ()) -> IO ()
foreign import ccall safe "ql.h qlFreeAdditionalResults" qlFreeAdditionalResults :: CUInt -> Ptr () -> IO ()
foreign import ccall safe "ql.h qlFreeStringArray" qlFreeStringArray :: CUInt -> Ptr CString -> IO ()
foreign import ccall safe "ql.h qlSavedSettings" qlSavedSettings :: IO (Ptr ())
foreign import ccall safe "ql.h qlFreeSavedSettings" qlFreeSavedSettings :: Ptr () -> IO ()
withLArray :: (Storable b) => (a -> b) -> [a] -> ((CUInt, Ptr b) -> IO c) -> IO c
withLArray c x f = withArray (map c x) (\px -> f (fromIntegral $ length x, px))
withEnumArray :: (Enum a) => [a] -> ((CUInt, Ptr CInt) -> IO b) -> IO b
withEnumArray = withLArray (fromIntegral . fromEnum)
withIntArray :: (Integral a, Num n, Storable n) => [a] -> ((CUInt, Ptr n) -> IO b) -> IO b
withIntArray = withLArray fromIntegral
withBoolArray :: [Bool] -> ((CUInt, Ptr CInt) -> IO b) -> IO b
withBoolArray = withLArray fromBool
withDoubleArray :: [Double] -> ((CUInt, Ptr CDouble) -> IO b) -> IO b
withDoubleArray = withLArray realToFrac
-- |Contiguous numeric data for APIs whose natural size is hundreds or thousands
-- of values. The public element type stays 'Double'; C's @double@ representation
-- is used only at the FFI boundary.
type RealVector = Vector Double
-- |A storable vector known not to be empty. The constructor is deliberately
-- hidden; build one with 'singletonNonEmptyVector', 'consNonEmptyVector', or
-- 'nonEmptyVector'.
newtype NonEmptyVector a = NonEmptyVector (Vector a)
singletonNonEmptyVector :: Storable a => a -> NonEmptyVector a
singletonNonEmptyVector = NonEmptyVector . V.singleton
consNonEmptyVector :: Storable a => a -> Vector a -> NonEmptyVector a
consNonEmptyVector x = NonEmptyVector . V.cons x
nonEmptyVector :: Storable a => Vector a -> Maybe (NonEmptyVector a)
nonEmptyVector x
| V.null x = Nothing
| otherwise = Just (NonEmptyVector x)
nonEmptyVectorToVector :: NonEmptyVector a -> Vector a
nonEmptyVectorToVector (NonEmptyVector x) = x
withNonEmptyRealVector :: NonEmptyVector Double -> ((CUInt, Ptr CDouble) -> IO b) -> IO b
withNonEmptyRealVector (NonEmptyVector x) = withRealVector x
-- |Borrows the vector's storage for one FFI call. QuantLib copies every
-- input vector it receives, so the vector need not outlive the continuation.
withRealVector :: RealVector -> ((CUInt, Ptr CDouble) -> IO b) -> IO b
withRealVector x f = V.unsafeWith x $ \p -> f (fromIntegral (V.length x), castPtr p)
-- |Like 'withRealVector', for an FFI argument whose length is carried separately.
withRealVectorRaw :: RealVector -> (Ptr CDouble -> IO b) -> IO b
withRealVectorRaw x f = V.unsafeWith x $ f . castPtr
withNonEmptyDoubleArray :: NonEmpty Double -> ((CUInt, Ptr CDouble) -> IO b) -> IO b
withNonEmptyDoubleArray x = withLArray realToFrac (toList x)
withDoubleArrayRaw :: [Double] -> (Ptr CDouble -> IO b) -> IO b
withDoubleArrayRaw x = withArray (map realToFrac x)
withBoolArrayRaw :: [Bool] -> (Ptr CInt -> IO b) -> IO b
withBoolArrayRaw x = withArray (map fromBool x)
withDayArray :: [Day] -> ((CUInt, Ptr CInt) -> IO b) -> IO b
withDayArray x f = mapM toSerial x >>= (`withArray` (\px -> f (fromIntegral $ length x, px)))
withDayPtr :: [Day] -> (Ptr CInt -> IO a) -> IO a
withDayPtr x f = mapM toSerial x >>= (`withArray` f)
-- |An array of plain C strings, for a function taking a @std::vector<std::string>@-shaped
-- argument as a flat @(count, char**)@ pair (the input-side counterpart of 'peekCStringArray'
-- below) -- first needed for @SecondaryCosts@' string keys (@QuantLib.Instrument.Energy@).
withStringArray :: [String] -> ((CUInt, Ptr CString) -> IO b) -> IO b
withStringArray xs f = withMany withCString xs (\ps -> withArray ps (\p -> f (fromIntegral (length xs), p)))
prePtr :: (Storable a) => (Ptr a -> IO b) -> IO b
prePtr = alloca
preArray :: ((Ptr CUInt, Ptr (Ptr a)) -> IO b) -> IO b
preArray f = with 0 $
\x -> with nullPtr $
\y -> f (x, y)
-- Protect every output from the FFI return, including arrays not yet converted by c2hs.
preArrayWith :: (CUInt -> Ptr a -> IO ()) -> ((Ptr CUInt, Ptr (Ptr a)) -> IO b) -> IO b
preArrayWith freeFn f = mask_ $ preArray $ \slots@(pl, pp) ->
f slots `finally` (do n <- peek pl; p <- peek pp; when (p /= nullPtr) (freeFn n p))
preIntArray :: ((Ptr CUInt, Ptr (Ptr CInt)) -> IO b) -> IO b
preIntArray = preArrayWith (const qlFreeInts)
preDoubleArray :: ((Ptr CUInt, Ptr (Ptr CDouble)) -> IO b) -> IO b
preDoubleArray = preArrayWith (const qlFreeDoubles)
preCStringArray :: ((Ptr CUInt, Ptr (Ptr CString)) -> IO b) -> IO b
preCStringArray = preArrayWith qlFreeStringArray
freePtrArray :: (Ptr a -> IO ()) -> CUInt -> Ptr (Ptr a) -> IO ()
freePtrArray freeOne n p = do
mapM_ (peekElemOff p >=> \x -> when (x /= nullPtr) (freeOne x)) [0 .. fromIntegral n - 1]
qlFreePointerArray (castPtr p)
prePtrArray :: (Ptr a -> IO ()) -> ((Ptr CUInt, Ptr (Ptr (Ptr a))) -> IO b) -> IO b
prePtrArray = preArrayWith . freePtrArray
-- Clear the slot while masked before taking responsibility for releasing its allocation.
consumeArray :: (CUInt -> Ptr a -> IO ()) -> Ptr CUInt -> Ptr (Ptr a) -> (CUInt -> Ptr a -> IO b) -> IO b
consumeArray freeFn pl pp convert = mask $ \restore -> do
n <- peek pl
p <- peek pp
poke pp nullPtr
restore (convert n p) `finally` freeFn n p
peekIntArray' :: (CInt -> b) -> Ptr CUInt -> Ptr (Ptr CInt) -> IO [b]
peekIntArray' f pl pp = consumeArray (const qlFreeInts) pl pp $ \n p ->
map f <$> peekArray (fromIntegral n) p
peekUIntArray :: Ptr CUInt -> Ptr (Ptr CUInt) -> IO [Word]
peekUIntArray pl pp = consumeArray (const qlFreeUInts) pl pp $ \n p ->
map fromIntegral <$> peekArray (fromIntegral n) p
peekIntArray :: Ptr CUInt -> Ptr (Ptr CInt) -> IO [Int]
peekIntArray = peekIntArray' fromIntegral
peekCStringArray :: Ptr CUInt -> Ptr (Ptr CString) -> IO [String]
peekCStringArray pl pp = consumeArray qlFreeStringArray pl pp $ \n p ->
peekArray (fromIntegral n) p >>= mapM peekCString
peekBoolArray :: Ptr CUInt -> Ptr (Ptr CInt) -> IO [Bool]
peekBoolArray = peekIntArray' toBool
peekDayArray :: Ptr CUInt -> Ptr (Ptr CInt) -> IO [Day]
peekDayArray = peekIntArray' fromSerial
peekDoubleArray :: Ptr CUInt -> Ptr (Ptr CDouble) -> IO [Double]
peekDoubleArray pl pp = consumeArray (const qlFreeDoubles) pl pp $ \n p ->
map realToFrac <$> peekArray (fromIntegral n) p
-- |Takes ownership of a C++-allocated @double[]@ result without copying it.
peekRealVector :: Ptr CUInt -> Ptr (Ptr CDouble) -> IO RealVector
peekRealVector pl pp = mask_ $ do
n <- fromIntegral <$> peek pl
p <- peek pp
poke pp nullPtr
fp <- newForeignPtr qlFreeDoublesFin p `onException` qlFreeDoubles p
pure (unsafeFromForeignPtr0 (castForeignPtr fp) n)
-- |A non-owning vector view valid only during the C++ callback.
borrowRealVector :: Ptr CDouble -> CUInt -> IO RealVector
borrowRealVector p n = do
fp <- castForeignPtr <$> newForeignPtr_ p
pure (unsafeFromForeignPtr0 fp (fromIntegral n))
-- |Convert before freeing: struct fields may borrow buffers owned by the aggregate.
peekStructArray :: Storable a => (a -> IO b) -> (CUInt -> Ptr a -> IO ()) -> Ptr CUInt -> Ptr (Ptr a) -> IO [b]
peekStructArray convert freeFn pl pp = consumeArray freeFn pl pp $ \n p ->
peekArray (fromIntegral n) p >>= mapM convert
-- |The converter owns an element only on success; cleared slots mark completed handoffs.
peekPtrArray :: (Ptr a -> IO ()) -> (Ptr a -> IO b) -> Ptr CUInt -> Ptr (Ptr (Ptr a)) -> IO [b]
peekPtrArray freeOne peekOne pl pp = mask_ $
consumeArray (freePtrArray freeOne) pl pp $ \n p ->
mapM (\i -> do
raw <- peekElemOff p i
x <- peekOne raw
pokeElemOff p i nullPtr
pure x) [0 .. fromIntegral n - 1]
fromEnumQuantity :: (Enum a, Integral b, Integral c) => (b, a) -> (CInt, c)
fromEnumQuantity (x, u) = (fromIntegral x, fromIntegral $ fromEnum u)
toEnumQuantity :: (Enum a, Integral b, Integral c) => (CInt, c) -> (b, a)
toEnumQuantity (x, u) = (fromIntegral x, toEnum $ fromIntegral u)
fromEnumDouble :: (Enum a, Integral c) => (Double, a) -> (CDouble, c)
fromEnumDouble (x, u) = (realToFrac x, fromIntegral $ fromEnum u)
toEnumDouble :: (Enum a, Integral c) => (CDouble, c) -> (Double, a)
toEnumDouble (x, u) = (realToFrac x, toEnum $ fromIntegral u)
foreign import ccall safe "ql.h qlMinYear" qlMinYear :: CInt
foreign import ccall safe "ql.h qlMinMonth" qlMinMonth :: CInt
foreign import ccall safe "ql.h qlMinDay" qlMinDay :: CInt
foreign import ccall safe "ql.h qlMinDateSerialNumber" qlMinDateSerialNumber :: CInt
foreign import ccall safe "ql.h qlMaxDateSerialNumber" qlMaxDateSerialNumber :: CInt
-- |Julian day of the QuantLib zero date
qlStart :: CInt
qlStart = minDateJulianDays - qlMinDateSerialNumber
where minDateJulianDays = toModifiedJulianDay' $ fromGregorian (fromIntegral qlMinYear) (fromIntegral qlMinMonth) (fromIntegral qlMinDay)
toModifiedJulianDay' :: Day -> CInt
toModifiedJulianDay' = fromIntegral . toModifiedJulianDay
fromSerial :: CInt -> Day
fromSerial x = ModifiedJulianDay $ fromIntegral (x + qlStart)
dayIsValid :: Day -> Bool
dayIsValid x = s >= qlMinDateSerialNumber && s <= qlMaxDateSerialNumber
where s = toModifiedJulianDay' x - qlStart
toSerial :: Day -> IO CInt
toSerial x | dayIsValid x = return $ toModifiedJulianDay' x - qlStart
| otherwise = throwIO $ DateConversion x
withDay :: Day -> (CInt -> IO a) -> IO a
withDay x f = toSerial x >>= f
toDay :: CInt -> Day
toDay = fromSerial
withMaybeDay :: Maybe Day -> (CInt -> IO a) -> IO a
withMaybeDay x f = maybe (f 0) (`withDay` f) x
-- |Unlike the -1 sentinel used by 'fromMaybeBool'/'fromMaybeEnum'/'toMaybeBool', the
-- absent-date sentinel is 0: QuantLib serial 0 is not a representable date (serials
-- start at 'qlMinDateSerialNumber'), so it is free to mean "no date". This matches
-- 'withMaybeDay', which passes 0 in the other direction.
toMaybeDay :: CInt -> Maybe Day
toMaybeDay 0 = Nothing
toMaybeDay x = Just $ fromSerial x
-- |earliest allowed date in QuantLib
minDate :: Day
minDate = fromSerial qlMinDateSerialNumber
-- |latest date allowed in QuantLib
maxDate :: Day
maxDate = fromSerial qlMaxDateSerialNumber
data Matrix a = Matrix {matrixRows::Word, matrixColumns::Word, matrixData::[a]}
deriving (Eq, Show)
-- |Row-major numeric matrix backed by contiguous storage. Use this for
-- large dense grids such as regression and volatility-surface data; small
-- process/correlation matrices remain boxed 'Matrix' values; object
-- matrices retain 'Matrix' because their elements need continuation-based FFI
-- marshalling rather than a raw contiguous pointer.
--
-- This representation interoperates directly with @hmatrix@ without making
-- @hasquant@ depend on it: @Numeric.LinearAlgebra.reshape cols
-- realMatrixData@ makes a row-major hmatrix matrix view with no element copy.
-- The reverse conversion through @flatten@ is zero-copy only for a contiguous
-- row-major hmatrix matrix; BLAS-produced or sliced matrices can require a
-- reorder/copy.
data RealMatrix = RealMatrix
{ realMatrixRows :: Word
, realMatrixColumns :: Word
, realMatrixData :: RealVector
}
deriving (Eq, Show)
-- |Construct a list-backed numeric matrix for small-dimensional APIs such as process
-- correlation and diffusion matrices. Returns a boxed 'Matrix' 'Double', /not/ a
-- 'RealMatrix' -- despite the shared @RealMatrix@ spelling this is not the pair of
-- 'realMatrixFromVector', which is the constructor for the contiguous 'RealMatrix' type.
-- Reach for this one when the dimensions are small and fixed (a correlation or diffusion
-- matrix), and for 'realMatrixFromVector' for large dense numerical grids such as
-- volatility surfaces and multi-asset LSM data.
boxedRealMatrix :: Word -> Word -> [Double] -> Either String (Matrix Double)
boxedRealMatrix = objectMatrix
-- |Construct a row-major numeric matrix backed by contiguous storage. This is the
-- constructor for 'RealMatrix'; for a small correlation\/diffusion matrix use
-- 'boxedRealMatrix', which yields a boxed 'Matrix' 'Double' instead.
realMatrixFromVector :: Word -> Word -> RealVector -> Either String RealMatrix
realMatrixFromVector rows cols d
| matrixDimensionsMatch rows cols (V.length d) = Right $ RealMatrix rows cols d
| otherwise = Left $ "Data length " ++ show (V.length d)
++ " does not match dimensions " ++ show rows ++ "x" ++ show cols
objectMatrix :: Word -> Word -> [a] -> Either String (Matrix a)
objectMatrix rows cols d
| matrixDimensionsMatch rows cols (length d) = Right $ Matrix rows cols d
| otherwise = Left $ "Data length " ++ show (length d)
++ " does not match dimensions " ++ show rows ++ "x" ++ show cols
-- |Checks a matrix shape without overflowing the public 'Word' dimensions.
matrixDimensionsMatch :: Word -> Word -> Int -> Bool
matrixDimensionsMatch rows cols dataLength =
toInteger rows * toInteger cols == toInteger dataLength
-- just a generic implementation to help when it's difficult to have Enum declaration due to complex module deps
fromEnumC :: (Enum a, Integral b) => a -> b
fromEnumC = fromIntegral . fromEnum
-- output-direction counterpart to 'fromEnumC', for a {#fun#} returning a bare enum value
-- (not through a pointer out-param, see 'peekEnum' for that case) across the same module-dep boundary
toEnumC :: (Enum a, Integral b) => b -> a
toEnumC = toEnum . fromIntegral
uncurryNested :: (a -> b -> c -> d) -> (a, (b, c)) -> d
uncurryNested f (x, (y, z)) = f x y z
-- |'Prelude' only goes up to 'zipWith3'; this fills the gap for unpacking a C-side
-- structure-of-parallel-arrays result into one Haskell record/tuple per element.
zipWith6 :: (a -> b -> c -> d -> e -> f -> g) -> [a] -> [b] -> [c] -> [d] -> [e] -> [f] -> [g]
zipWith6 f (a:as) (b:bs) (c:cs) (d:ds) (e:es) (g:gs) = f a b c d e g : zipWith6 f as bs cs ds es gs
zipWith6 _ _ _ _ _ _ _ = []
-- vim: set ff=unix ts=8 sts=2 sw=2 et:
-- Scalar components keep the FFI independent of C++ complex layout.
withComplex :: Complex Double -> (CDouble, CDouble)
withComplex (r :+ i) = (realToFrac r, realToFrac i)
preComplex :: ((Ptr CDouble, Ptr CDouble) -> IO a) -> IO a
preComplex f = alloca $ \r -> alloca $ \i -> f (r, i)
peekComplex :: Ptr CDouble -> Ptr CDouble -> IO (Complex Double)
peekComplex r i = (:+) . realToFrac <$> peek r <*> (realToFrac <$> peek i)