Haskell外来类型的习惯用法?

时间:2016-12-18 00:31:11

标签: c haskell ffi

问题:从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_StructHS_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之间来回传播的疯狂数量。

问题

  1. 有没有办法尽量减少上面的样板?
  2. 对于涉及大量FFI的Haskell项目,只是放弃并主要使用Haskell的C类型(即CIntCString等等)是不是惯用的?这至少可以为您节省必须在类型之间来回转换的难题。

1 个答案:

答案 0 :(得分:0)

“成语地”,我认为无法绕过C和Haskell之间的编组值的样板。但是,对于您的问题,您可能会发现有用的答案。在为a number of libraries编写并贡献了内容后,为Haskell用户包装了C库之后,我强烈建议您使用bindings-dsl库来解决您的问题,在幕后使用hsc2hs。具体来说,是为了回答您的问题:

1。有什么方法可以最小化上面的样板?

您可以消除其中的一些,但是在大多数情况下,C类型和Haskell类型之间的编组处理需要格外小心,以确保Haskell值保持良好的基础。这是一个功能,而不是错误,因为C类型本质上是不同的表示形式。例如,对于StringCString,您需要指定呼叫时发生的情况

makeStruct $ HS_DataStructure (repeat 'a') (repeat 'b') 0

...或如何处理内存分配(如果没有相应的deleteStruct函数,您的示例将泄漏)。同样,对于IntCInt的整数语义也存在担忧。如果您得到的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库接口之间保持一致的方式来实现样板。