问题:从Haskell类型转换为Foreign类型并返回需要大量样板代码。
例如,假设我们正在使用以下Haskell数据结构:
data HS_DataStructure = HS_DataStructure {
a1 :: String
, b1 :: String
, c1 :: Int
}
为了使此数据结构进入C语言领域,我们将需要考虑其
struct
类似物:typedef struct {
char *a;
char *b;
int c;
} c_struct;
但是为了将这种结构从Haskell传递到C,我们必须将
HS_DataStructure
转换为以下代码:data HS_Struct = HS_Struct {
a :: CString
, b :: CString
, c :: CInt
} deriving Show
然后,我们必须使
HS_Struct
成为Storable
的实例:instance Storable HS_Struct where
sizeOf _ = #{size c_struct}
alignment _ = alignment (undefined :: CString)
poke p c_struct = do
#{poke c_struct, a} p $ a c_struct
#{poke c_struct, b} p $ b c_struct
#{poke c_struct, c} p $ c c_struct
peek p = return HS_Struct
`ap` (#{peek c_struct, a} p)
`ap` (#{peek c_struct, b} p)
`ap` (#{peek c_struct, c} p)
(在上面,我正在使用hs2c语法)。
现在,最后,为了在
HS_Struct
和HS_DataStructure
之间进行转换,我们被迫使用以下辅助函数(!):makeStruct :: HS_DataStructure -> IO (HS_Struct)
makeStruct hsds = do str1 <- newCString (a1 hsds)
str2 <- newCString (b1 hsds)
jreturn (HS_Struct str1 str2 (c1 hsds))
makeDataStructure :: Ptr (HS_Struct) -> IO (HS_DataStructure)
makeDataStructure p = do hss <- peek p
hs1 <- peekCString (a hss)j
hs2 <- peekCString (b hss)
return (HS_DataStructure hs1 hs2 (c hss))
这似乎是在Haskell和C之间来回移动的疯狂模板。
问题
CInt
,CString
等)是惯用的吗?这至少可以节省您在类型之间来回转换的麻烦。 最佳答案
“习惯上”,我认为没有办法解决C和Haskell之间的值编组问题。但是,对于您的问题,您可能会发现有用的答案。在为为Haskell用户包装了C库的a number of libraries编写并做出贡献之后,我强烈建议您使用bindings-dsl库来解决您的问题,该库在后台使用了hsc2hs。具体来说,为回答您的问题:
1.有什么方法可以最小化上面的样板?
您可以消除其中的一些,但是在大多数情况下,在C类型和Haskell类型之间进行编组时需要格外小心,以确保Haskell值保持良好的基础。这是一个功能,而不是错误,因为C类型本质上是不同的表示形式。例如,对于String
和CString
,您需要指定调用时发生的情况
makeStruct $ HS_DataStructure (repeat 'a') (repeat 'b') 0
...或如何处理内存分配(如果没有相应的deleteStruct
函数,您的示例将泄漏)。同样,对于Int
和CInt
的整数语义也存在担忧。如果您获得的CInt
超出了Int
的范围,会发生什么?钳?包?这些答案通常是特定于应用程序的,并且与其他库的互操作性要求不变性适用于所有Haskell程序。使用bindings-dsl,我们至少可以通过使用以下内容定义
Storable
文件来摆脱编写自己的.hsc
实例的需要:module MyModule where
#include <c_struct.h>
#starttype struct HS_Struct
#field a, CString
#field b, CString
#field c, CInt
#stoptype
如果将此模块添加到cabal文件中,cabal应该会认识到它需要使用hsc2hs
,并将正确编译它并添加所有其他实例。查看上面的任何链接以获取示例。您的makeDataStructure
代码也可以变得更简单:makeDataStructure :: Ptr HS_Struct -> IO HS_DataStructure
makeDataStructure p = do
HS_Struct ca cb cc <- peek p
HS_DataStructure <$> peekCString ca <*> peekCString cb <*> fromIntegral cc
使用bindings-DSL
的真正“样板减少”优势来自 header 中存在的函数Haskell-side FFI definitions。2.对于涉及大量FFI的Haskell项目,仅屈服并主要使用Haskell的C类型(即CInt,CString等)是惯用的吗?这至少可以节省您在类型之间来回转换的麻烦。
具有“惯用性”的东西有点主观(因此容易受到自行车脱落和关门的影响),因此,如果您对这个问题有任何答案,都应该加一点盐,可能不是最适合该站点的方法。在我看来,我不认为主要使用C类型是惯用的。这些类型仅用于与C的接口(interface),并且Haskell运行时针对Haskell类型进行了优化。如果您发现自己编写了太多的FFI,以至于需要主要使用FFI类型,则可能很好地表明您应该编写C库。
当然,如果您正在编写包装了C库的Haskell库,则例外。在这种情况下,我认为Haskell库的重点在于以一种在Haskell运行时和C库接口(interface)之间保持一致的方式来实现样板。