我正在编写一个与大型(10-1000 GB)内存映射二进制文件交互的应用程序,该文件基本上包含一堆相互引用的对象。我想出了一种机制来读/写这些数据,它是有效的,但丑陋而冗长(imo)。
问:有没有更优雅的方式来实现我所做的事情?
我有一个用于结构化数据的类型类,其中一种方法可以将结构读入 Haskell 数据类型(DataOp
是 a ReaderT
around IO
)。
class DBStruct a where
structRead :: Addr a -> DataOp a
为了使其更具可读性,我有另一个类型类,它定义了结构成员的去向:
class DBStruct st => StructMem structTy valTy name | structTy name -> valTy where
offset :: structTy -> valTy -> name -> Int64
我有一些辅助函数使用 offset 方法来读取/写入结构元素,从存储的引用中读取结构,以及延迟结构读取(以允许延迟读取整个文件)。
这样做的问题是它涉及大量重复使用。对于一个结构,我首先必须定义 Haskell 类型:
data RowBlock = RowBlock {rbNext :: Maybe RowBlock
,rbPrev :: Maybe RowBlock
,rbRows :: [RowTy]
}
然后是name
类型:
data Next = Next
data Prev = Prev
data Count = Count
newtype Row = Row Int64
然后是每个结构成员的实例:
instance StructMem RowBlock (Maybe (Addr RowBlock)) Next where offset _ _ _ = 0
instance StructMem RowBlock (Maybe (Addr RowBlock)) Prev where offset _ _ _ = 8
instance StructMem RowBlock Int64 Count where offset _ _ _ = 16
instance StructMem RowBlock RowTy Row where offset _ _ (Row n) = 24 + n * 8
然后结构体读取方法:
instance DBStruct RowBlock where
structRead a = do
n <- elemMaybePtr a Next
p <- elemMaybePtr a Prev
c <- elemRead a Count
rs <- mapM (elemRead a . Row) [0 .. c-1]
return $ RowBlock n p rs
所以我真正完成的只是以更冗长(和缓慢)的方式重新实现 C 结构。如果这在保持类型安全的同时更简洁,我会更高兴。当然,这是一个经常遇到的问题。
我能想到的几个可能的替代方案是:
- 丢弃内存映射文件并使用
Data.Binary
,ByteStrings
以正常方式将其写入磁盘。 - 用于
deriving Generic
创建通用读写函数 - 重载函数引用
- 用一元镜头做一些神奇的事情。
编辑:SSCCE 按要求