问题:从Haskell类型转到外部类型并返回需要大量的样板代码.
例如,假设我们正在使用以下Haskell数据结构:
data HS_DataStructure = HS_DataStructure {
a1 :: String
, b1 :: String
, c1 :: Int
}
Run Code Online (Sandbox Code Playgroud)
为了使这个数据结构进入C的土地,我们需要考虑它的struct类比:
typedef struct {
char *a;
char *b;
int c;
} c_struct;
Run Code Online (Sandbox Code Playgroud)
但是为了将这样的结构从Haskell传递给C,我们必须转换HS_DataStructure为以下内容:
data HS_Struct = HS_Struct {
a :: CString
, b :: CString
, c :: CInt
} deriving Show
Run Code Online (Sandbox Code Playgroud)
然后我们必须做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)
Run Code Online (Sandbox Code Playgroud)
(在上面我使用的是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))
Run Code Online (Sandbox Code Playgroud)
这似乎是在Haskell和C之间来回传播的疯狂数量的样板.
CInt,CString,等等)?这至少可以为您节省必须在类型之间来回转换的麻烦.“惯用地”,我认为没有办法绕过 C 和 Haskell 之间编组值的样板。不过,您可能会发现一些对您的问题有用的答案。在为 Haskell 用户编写并贡献了许多包装 C 库的 库之后,我强烈建议使用Bindings-dsl库来解决您的问题,该库在底层使用了 hsc2hs 。具体来说,回答你的问题:
1. 有什么办法可以最小化上面的样板吗?
您可以消除其中一些,但在大多数情况下,C 类型和 Haskell 类型之间的封送需要小心确保 Haskell 值保持良好的基础。这是一个功能,而不是一个错误,因为 C 类型本质上是不同的表示形式。例如,对于String和CString,您需要指定如果调用会发生什么
makeStruct $ HS_DataStructure (repeat 'a') (repeat 'b') 0
Run Code Online (Sandbox Code Playgroud)
...或如何处理内存分配(您的示例将在没有相应deleteStruct函数的情况下泄漏)。同样,也存在对 和 的整数语义的Int担忧CInt。CInt如果你得到的 a超出了范围,会发生什么Int?夹钳?裹?这些答案通常是特定于应用程序的,并且与其他库的互操作性要求这些不变量适用于所有 Haskell 程序。
使用绑定-dsl ,我们至少可以通过使用以下内容定义文件来摆脱编写自己的Storable实例的需要:.hsc
module MyModule where
#include <c_struct.h>
#starttype struct HS_Struct
#field a, CString
#field b, CString
#field c, CInt
#stoptype
Run Code Online (Sandbox Code Playgroud)
如果您将此模块添加到 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
Run Code Online (Sandbox Code Playgroud)
使用真正的“样板文件减少”优势bindings-DSL来自于头文件中存在的Haskell 端 FFI 函数定义。
2. 对于涉及大量 FFI 的 Haskell 项目,是否习惯于放弃并主要使用 Haskell 的 C 类型(即 CInt、CString 等)?这至少可以省去您在类型之间来回转换的麻烦。
让某些东西成为“惯用”有点主观(因此容易受到自行车停放和把关的影响),因此您对这个问题的任何答案都应该持保留态度,并且可能不是最适合这个网站的。在我看来,我不认为主要使用 C 类型是惯用的。这些类型的存在只是为了与 C 接口,并且 Haskell 运行时是围绕 Haskell 类型进行优化的。如果您发现自己编写了如此多的 FFI,以至于您觉得需要主要使用 FFI 类型,那么这可能是一个很好的迹象,表明您应该编写一个 C 库。
当然,例外情况是,如果您正在编写一个包装 C 库的 Haskell 库。在这种情况下,我认为 Haskell 库的全部要点是以 Haskell 运行时和 C 库接口之间一致的方式实现样板。