设置
假设您有一个典型的C API,其中分配了一些结构,然后初始化(分配内存,创建句柄等),然后使用各种函数进行处理(转换,查询等),最后再次释放。即标准的C风格的OO框架,其中struct是'object',函数是'member functions'。
更具体地说,让API适用于字节串。例如,这可能是一个压缩库。
在Haskell中,Data.ByteString.ByteString
,在最低级别,只是“GC堆中的固定内存”(用mallocPlainForeignPtrBytes
分配),指针是{{1的第一个参数构造函数PS
。可以使用Data.ByteString.Internal.toForeignPtr
获取ByteString
的基础内存。到目前为止一切都很好。
传递ByteString
让Haskell管理C API运行的ByteString
很方便。在C中,我们可能有一个struct和init函数,如下所示:
ByteString
typedef struct {
uint8_t* buf;
size_t buflen;
} API_t;
void API_init_as_writer(API_t* p, uint8_t* buf, size_t buflen);
和buf
持有Haskell buflen
。该对象成为“作家”。 Haskell绑定看起来像这样:
ByteString
使用toForeignPtr
和withForeignPtr
获取 data API -- empty decl (needs EmptyDataDecls extension)
foreign import ccall unsafe "API.h API_init_as_writer"
init :: Ptr API -> Ptr Word8 -> Int -> IO ()
。
返回(传递)Ptr Word8
同样,我们可以让对象(struct)成为'读者'并使用ByteString
和ByteString
创建mallocPlainForeignPtrBytes
,如上所述。
垃圾收集阻碍了!
问题在于,在调用API_init_ *函数之后,垃圾收集器不知道正在使用PS
(通过保存指向GC堆内存的指针的结构)。在“作家”的情况下:
ByteStrings
以及'读者'的情况:
writer <- initWriter 1024 -- inits with buflen of 1024 via mallocPlainForeignPtrBytes
writeSomething writer
writeSomethingElse writer
...
bs <- getByteString writer -- construct ByteString with PS
writeFile "Some.file" bs
所有这些都发生在IO monad中。
显然,这不起作用。在 bs <- ByteString.readFile("Some.file")
reader <- initReader bs
info1 <- readSomething reader
info2 <- readSomethingElse reader
或ByteString
之后,initWriter
可能会收集垃圾,而initReader
或writeSomething
函数将继续使用垃圾回收内存。
尽管存在任何GC问题,但就像{mon}中的readSomething
已经“不安全”一样,这些API ByteString
操作也不应该在IO monad中,因为实际上没有任何IO或与ByteString
发生的互动。
所以问题是:如何在Haskell中正确构建这个结构?
This所以问题会导致使用RealWorld
的暗示,这不是很好。
我的预感是,这需要实现功能。我猜这些OO类型API的顺序特性(alloc,init,操作,操作,...,免费)需要引入除IO monad之外的monad。但我不太清楚如何正确设置它。当我查看数据库绑定时,我只看到IO monad。例如,db_get
的BerkeleyDB binding函数返回IORef
- 不会出现GC问题,因为IO (Maybe ByteString)
是查询的结果。我现在有点困惑。我想我正在达到OO和FP之间的摩擦点。或许我完全以错误的方式看待这个。
更新1
正如@Alec指出的那样,使用touchForeignPtr
可以缓解(解决?)GC问题。尽管如此,我仍然非常倾向于将其从IO monad中移除,以获得更清晰的界面。