{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RecursiveDo #-}
{-|
This module defines 'StaticVector', a simple wrapper around
'VG.Vector' with the dimensions in the type. 'StaticVector' provides a
'Storable' instance using the type-level dimensions. This eases
writing FFI bindings to fixed-size native arrays.
Support for interop with multi-dimensional native arrays is provided
via the 'IxStatic' class. This results in the slightly unnatural case
where you might need to convert 'Ix' coordinates to `VG.Vector`
indices, but it felt like an acceptable tradeoff when interfacing with
multi-dimensional native arrays.
-}
module Foreign.Marshal.StaticVector
( StaticVector
, toVector
, staticBounds
, staticSize
, fromList
) where
import Control.Monad
import Data.Functor ((<$>))
import Data.Ix
import Data.Ix.Static
import qualified Data.Vector.Generic as VG
import qualified Data.Vector.Generic.Mutable as VGM
import Data.Tagged
import Foreign.Ptr
import Foreign.Storable
import Foreign.Marshal.Array
-- | A minimal 'VG.Vector' wrapper that encodes the full dimensions of
-- the array in the type. Intended for interfacing with
-- (possibly-)multidimensional arrays of fixed size in native code.
--
-- If this is used with multidimensional arrays, it will be up to
-- users to deal with converting 'Ix' coordinates to internal
-- 'VG.Vector' indices.
--
-- The constructor is not exported to prevent creating a
-- 'StaticVector' with a size that doesn't match its dimensions.
newtype StaticVector backing dimensions (elements :: *) =
StaticVector {
-- | Returns the backing value of this 'StaticVector'.
toVector :: backing elements
}
deriving (Eq)
instance (VG.Vector b e, Show e) => Show (StaticVector b d e) where
show = ("fromList " ++) . show . VG.toList . toVector
-- | Get the compile-time bounds from a 'StaticVector'. Does not
-- examine its argument.
{-# INLINEABLE staticBounds #-}
staticBounds :: forall b d e. IxStatic d =>
StaticVector b d e -> (Index d, Index d)
staticBounds _ = untag (taggedBounds :: Tagged d (Index d, Index d))
-- | Get the compile-time size from a 'StaticVector'. Does not examine
-- its argument.
{-# INLINEABLE staticSize #-}
staticSize :: IxStatic d => StaticVector b d e -> Int
staticSize = rangeSize . staticBounds
-- | Create a new 'StaticVector' with the contents of the list. If the
-- list passed in contains too many elements, the result will be
-- truncated. If it contains too few elements, they will be cycled to
-- pad out the remaining space. If it contains 0 elements, this will
-- result in an error.
{-# INLINEABLE fromList #-}
fromList :: (VG.Vector b e, IxStatic d) => [e] -> StaticVector b d e
fromList els | null els = error "empty input to fromList"
| otherwise = sv
where
size = staticSize sv
sv = StaticVector . VG.fromListN size . cycle $ els
instance (IxStatic d, Storable e, VG.Vector b e) =>
Storable (StaticVector b d e) where
{-# INLINEABLE sizeOf #-}
sizeOf a = sizeOf (undefined :: e) * rangeSize (staticBounds a)
{-# INLINEABLE alignment #-}
alignment _ = alignment (undefined :: e)
{-# INLINEABLE poke #-}
poke dst' sv@(StaticVector v) = do
let upper = staticSize sv - 1
dst = castPtr dst'
forM_ [0..upper] $ \i -> poke (advancePtr dst i) $ VG.unsafeIndex v i
{-# INLINEABLE peek #-}
peek src' = do
rec let size = staticSize sv
v <- VGM.unsafeNew size
let src = castPtr src'
forM_ [0 .. size - 1] $ \i -> do
x <- peek $ advancePtr src i
VGM.unsafeWrite v i x
sv <- StaticVector <$> VG.unsafeFreeze v
return sv