我有以下表格的数据结构(V是Data.Storable.Vector):
data Elems = I {-# UNPACK #-} !GHC.Int.Int32
| S {-# UNPACK #-} !GHC.Int.Int32 {-# UNPACK #-} !(Ptr CChar)
| T {-# UNPACK #-} !(V.Vector Elems)
deriving (Show)
我首先为非递归形式编写了一个自定义可存储定义(即没有T
构造函数)。然后,我尝试使用T
中的ForeignPtr
和length
信息为Vector
添加自定义查看和戳定义(代码如下)。 GHC编译器抱怨Storable
实例没有为ForeignPtr Elems
类型定义。我的问题是,是否可以将ptr存储到可存储定义中的Vector,而不必强制为ForeignPtr编写可存储的实例定义。
从Haddocs文档中,ForeignPtr似乎只是一个分配了Finalizer的Ptr:
ForeignPtrs和香草记忆之间的本质区别 Ptr a类型的引用是前者可能与之相关联的 终结。
由于最终确定问题,我不想使用Ptr
代替ForeignPtr
来解决此问题。因此,我更喜欢存储ForeignPtr的位置(通过Ptr (ForeignPtr a)
),以便GHC垃圾收集器知道对它的引用。但是,这种方法会迫使我定义Storable instance
(因为约束(Storable a) => Ptr a
这是有道理的)。
有没有办法将Ptr存储和检索到Storable中的Vector,而无需为ForeignPtr定义可存储的实例?如果没有,那么编写ForeignPtr的可存储定义是必须的。在那种情况下,它会是什么样子?我的猜测是它只会将一个Ptr存储到ForeignPtr。
以下完整代码:
{-# LANGUAGE MagicHash #-}
import qualified Data.Vector.Storable as V
import Foreign
import Foreign.C.Types (CChar)
import Foreign.Marshal.Array (lengthArray0)
import GHC.Int
data Elems = I {-# UNPACK #-} !GHC.Int.Int32
| S {-# UNPACK #-} !GHC.Int.Int32 {-# UNPACK #-} !(Ptr CChar)
| T {-# UNPACK #-} !(V.Vector Elems)
deriving (Show)
instance Storable Elems where
sizeOf _ = sizeOf (undefined :: Word8) + sizeOf (undefined :: Int32) + sizeOf (undefined :: Ptr CChar)
alignment _ = 4
{-# INLINE peek #-}
peek p = do
let p1 = (castPtr p::Ptr Word8) `plusPtr` 1 -- get pointer to start of the element. First byte is type of element
t <- peek (castPtr p::Ptr Word8)
case t of
1 -> do
x <- peek (castPtr p1 :: Ptr GHC.Int.Int32)
return (I x)
2 -> do
x <- peek (castPtr p1 :: Ptr GHC.Int.Int32)
y <- peek (castPtr (p1 `plusPtr` 4) :: Ptr (Ptr CChar)) -- increment pointer by 4 bytes first
return (S x y)
_ -> do
x <- peek (castPtr p1 :: Ptr Int)
y <- peek (castPtr (p1 `plusPtr` 8) :: Ptr (ForeignPtr Elems))
return (T (V.unsafeFromForeignPtr y 0 x)) -- return vector
{-# INLINE poke #-}
poke p x = case x of
I a -> do
poke (castPtr p :: Ptr Word8) 1
poke (castPtr p1) a
S a b -> do
poke (castPtr p :: Ptr Word8) 2
poke (castPtr p1) a
poke (castPtr (p1 `plusPtr` 4)) b -- increment pointer by 4 bytes first
T x -> do
poke (castPtr p :: Ptr Word8) 3
let (fp,_,n) = V.unsafeToForeignPtr x
poke (castPtr p1) n
poke (castPtr (p1 `plusPtr` 8)) fp
where p1 = (castPtr p :: Ptr Word8) `plusPtr` 1 -- get pointer to start of the element. First byte is type of element
答案 0 :(得分:3)
ForeignPtr
无法成为Storable
,因为它们的实现需要一种方法将一个或多个终结器指针与原始指针相关联,并且此关联与运行时相关。要使ForeignPtr
可存储,您需要存储关联的Ptr
(这很容易)和关联的终结器数组(这是不可能的,因为终结器是运行时内部的,并且可能绑定到GHC运行时的GC。)
这不是这里需要解决的问题。
问题在于,没有合理的方法可以将包含Vector
的内容添加到Storable
内。 Vector
要求托管内存的内容(Storable.Vector
的定义为data Vector a = Vector Int (ForeignPtr a)
加上一些严格注释),但Storable
的全部目的是将一些值存储到非托管内存中。此外,Vector
根据其长度使用不同数量的内存,但Storable
数据结构必须使用常量内存量。
您需要重新考虑数据结构尝试建模的内容。你真的需要存储这样的Vector
吗?请注意,您要存储Vector
Elems
,这意味着您的值T
包含Vector
,其中包含T
,其中包含{ {1}}包含Vector
等
我认为您可能会尝试对以下数据结构进行建模,但我可能错了:
T
如果您确实需要所描述的递归数据结构,请尝试实现此目的:
data Elems = OneElem Elem | ManyElems (Vector Elem)
data Elem
= I !GHC.Int.Int32
| S !GHC.Int.Int32 !(Ptr CChar)
指向某些data Elems
= I !GHC.Int.Int32
| S !GHC.Int.Int32 !(Ptr CChar)
| T !GHC.Int.Int32 !(Ptr Elems)
的指针使用常量内存,并且可以指向非托管内存,因此您可以为其创建可存储的实例。