问题:从Haskell类型转到外部类型并返回需要很多样板代码。
例如,假设我们正在使用以下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
等等)是不是惯用的?这至少可以为您节省必须在类型之间来回转换的难题。答案 0 :(得分:0)
“成语地”,我认为无法绕过C和Haskell之间的编组值的样板。但是,对于您的问题,您可能会发现有用的答案。在为a number of libraries编写并贡献了内容后,为Haskell用户包装了C库之后,我强烈建议您使用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
的真正“样板减少”优势来自标题中存在的功能Haskell-side FFI definitions。
2。对于涉及大量FFI的Haskell项目,仅屈服并主要使用Haskell的C类型(即CInt,CString等)是惯用的吗?至少可以节省您在类型之间来回转换的麻烦。
使某些东西“惯用”是有点主观的(因此容易受到自行车脱落和关门的影响),因此,对于此问题的任何答案都应加盐,可能不是最合适的选择现场。我认为主要使用C类型不是惯用语言。这些类型仅用于与C的接口,并且Haskell运行时针对Haskell类型进行了优化。如果您发现自己编写了太多的FFI,以至于需要主要使用FFI类型,则可能表明您应该编写C库。
当然,如果您正在编写包装了C库的Haskell库,则例外。在这种情况下,我认为Haskell库的重点在于以一种在Haskell运行时和C库接口之间保持一致的方式来实现样板。