在Haskell中为FFI调用自动转换类型 [英] Automatic conversion of types for FFI calls in Haskell
问题描述
{ - #LANGUAGE MultiParamTypeClasses,FunctionalDependencies,TypeSynonymInstances # - }
模块ExportFFI其中
导入外部
导入Foreign.C
class FFI基本ffitype |基本 - > ffitype where
toFFI :: basic - > IO ffitype
fromFFI :: ffitype - > IO basic
freeFFI :: ffitype - > IO()
实例FFI字符串CString其中
toFFI = newCString
fromFFI = peekCString
freeFFI = free
我在为函数实例挣扎。有人可以帮我吗?
您可以使用涉及FFI的功能做两件事:
1)编组:这意味着将功能转换为可以通过FFI输出的类型。这由 FunPtr
完成。
2)导出:这意味着创建一个非Haskell代码调用Haskell函数的方法。
您的FFI类有助于编组,创建一个如何编组函数的示例实例。
这是未经测试的,但它编译,我希望它能工作。首先,让我们稍微改变一下类:
class FFI basic ffitype |基本 - > ffitype,ffitype - >基本在哪里
toFFI :: basic - > IO ffitype
fromFFI :: ffitype - > IO basic
freeFFI :: ffitype - > IO()
这表示给定basic或ffitype的类型,是固定的[1]。这意味着不可能将两个不同的值编组成相同的类型,例如,你不能再同时拥有这两个元素:
实例FFI Int CInt其中
实例FFI Int32 CInt其中
原因是因为 freeFFI
不能按照你的定义使用;没有办法确定从ffitype中选择哪个实例。或者,您可以将类型更改为 freeFFI :: ffitype - >基本 - > IO()
或(更好?) freeFFI :: ffitype - > IO基本
。然后你根本不需要fundeps。
分配FunPtr的唯一方法是使用外部导入语句,该语句仅适用于完全实例化的类型。您还需要启用 ForeignFunctionInterface
扩展名。因此, toFFI
函数应该返回一个 IO(FunPtr x)
,不能是多态函数的函数类型。换句话说,你需要这个:
foreign import ccallwrapper
mkIntFn ::(Int32 - > Int32) - > IO(FunPtr(Int32 - > Int32))
foreign import ccalldynamic
dynIntFn :: FunPtr(Int32 - > Int32) - > (Int32 - > Int32)
实例FFI(Int32 - > Int32)(FunPtr(Int32 - > Int32))其中
toFFI = mkIntFn
fromFFI = return。 dynIntFn
freeFFI = freeHaskellFunPtr
用于您想编组的所有不同功能类型。您还需要此实例的 FlexibleInstances
扩展名。 FFI有一些限制:每种类型都必须是可编组的外部类型,函数返回类型必须是可编组的外部类型或返回可编组外部类型的IO操作。
对于非编组类型(例如字符串),您需要稍微复杂一些的东西。首先,由于编组发生在IO中,因此只能编组导致IO操作的函数。
如果你想编组纯函数,例如(String - > String),你需要将它们提升到窗体(String - > IO String)。[2]让我们定义两个助手:
wrapFn ::(FFI a ca,FFI b cb)=> (a→IO b)→> (ca - > IO cb)
wrapFn fn = fromFFI> => fn> => toFFI
unwrapFn ::(FFI a ca,FFI b cb)=> (ca→IOcb)→> (a - > 10 b)
unwrapFn fn a =括号(toFFI a)freeFFI(fn> => fromFFI)
这些将函数的类型转换为合适的编组值,例如 wrapStrFn ::(String - > IO String) - > (CString - > IO CString); wrapStrFn = wrapFn
。请注意, unwrapFn
使用Control.Exception.bracket来确保资源在异常情况下被释放。忽略这个,你可以写 unwrapFn fn = toFFI> => fn> => fromFFI
;看到与wrapFn的相似性。
现在我们有了这些助手,我们可以开始编写实例:
foreign import ccallwrapper
mkStrFn ::(CString - > IO CString) - > IO(FunPtr(CString - > IO CString))
foreign import ccalldynamic
dynStrFn :: FunPtr(CString - > IO CString) - > (CString - > IO CString)
instance FFI(String - > IO String)(FunPtr(CString - > IO CString))其中
toFFI = mkStrFn。 wrapFn
fromFFI = return。 unwrapFn。 dynStrFn
freeFFI = freeHaskellFunPtr
与以前一样,不可能使这些函数具有多态性,导致了我对这个系统最大的保留。这是很多开销,因为您需要为每种类型的函数创建单独的包装器和实例。除非你在做大量的函数编组,否则我会认真地怀疑这是值得的。
这就是你可以编组函数的方法,但是如果你想要他们可用于调用代码?这个其他过程是导出函数,并且我们已经开发了大部分必要的功能。
导出的函数必须具有可编组类型,就像 FunPtr
秒。我们可以简单地重复使用 wrapFn
来做到这一点。要导出一些函数,你只需要用 wrapFn
包装它们并导出包装的版本:
f1 :: Int - > Int
f1 =(+2)
f2 :: String - >字符串
f2 =反向
f3 :: String - > IO Int
f3 =返回。长度
国外出口ccall f1Wrapped :: CInt - > IO CInt
f1Wrapped = wrapFn(return。f1)
国外出口ccall f2Wrapped :: CString - > IO CString
f2Wrapped = wrapFn(return。f2)
国外导出ccall f3Wrapped :: CString - > IO CInt
f3Wrapped = wrapFn f3
不幸的是,这个设置只适用于单参数函数。为了支持所有的功能,我们再创建一个类:
class ExportFunction a b其中
exportFunction :: a - > b
实例(FFI a ca,FFI b cb)=> ExportFunction(a-> b)(ca - > IO cb)其中
exportFunction fn =(wrapFn(return .fn))
实例(FFI a ca,FFI b cb, FFI d cd)=> ExportFunction(a-> b-> d)(ca-> cb-> IO cd)其中
exportFunction fn = \ca cb - >做
a < - fromFFI ca
b < - fromFFI cb
toFFI $ fn ab
现在我们可以对带有1和2个参数的函数使用 exportFunction
:
f4 :: Int - > Int - > Int
f4 =(+)
f4Wrapped :: CInt - > CInt - > IO CInt
f4Wrapped = exportFunction f4
国外出口ccall f4Wrapped :: CInt - > CInt - > IO CInt
f3Wrapped2 = :: CString - > IO CInt
f3Wrapped2 = exportFunction f3
国外出口ccall f3Wrapped2 :: CString - > IO CInt
f3Wrapped2 = exportFunction f3
现在您只需编写 ExportFunction
自动将任何函数转换为适当的导出类型。我认为如果不使用某种类型的预处理器或unsafePerformIO,这是最好的。
[1]从技术上讲,我不认为有任何需要对于基本 - > ffitypefundep,所以你可以删除它以使一个基本类型映射到多个ffitypes。这样做的一个原因是将所有大小的整数映射到整数,但 toFFI
实现将是有损的。
[2]略微简化。你可以编组一个函数 String - >字符串
添加到 CString - >的FFI类型中。 IO CString
。但是现在你不能转换 CString - > IO CString
函数回到 String - > String
因为返回类型中的IO。
I have defined the following module to help me with FFI function export:
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, TypeSynonymInstances #-}
module ExportFFI where
import Foreign
import Foreign.C
class FFI basic ffitype | basic -> ffitype where
toFFI :: basic -> IO ffitype
fromFFI :: ffitype -> IO basic
freeFFI :: ffitype -> IO ()
instance FFI String CString where
toFFI = newCString
fromFFI = peekCString
freeFFI = free
I'm struggling with the instance for functions. Can someone help me?
There are two things you can do with functions involving the FFI:
1) Marshalling: this means converting a function to a type that can be exported through the FFI. This accomplished by FunPtr
.
2) Exporting: this means creating a means for non-Haskell code to call into a Haskell function.
Your FFI class helps with marshalling, and first I create a few sample instances of how to marshal functions.
This is untested, but it compiles and I expect it would work. First, let's change the class slightly:
class FFI basic ffitype | basic -> ffitype, ffitype -> basic where
toFFI :: basic -> IO ffitype
fromFFI :: ffitype -> IO basic
freeFFI :: ffitype -> IO ()
This says that given the type of either "basic" or "ffitype", the other is fixed[1]. This means it's no longer possible to marshal two different values to the same type, e.g. you can no longer have both
instance FFI Int CInt where
instance FFI Int32 CInt where
The reason for this is because freeFFI
can't be used as you've defined it; there's no way to determine which instance to select from just the ffitype. Alternatively you could change the type to freeFFI :: ffitype -> basic -> IO ()
, or (better?) freeFFI :: ffitype -> IO basic
. Then you wouldn't need fundeps at all.
The only way to allocate a FunPtr is with a "foreign import" statement, which only works with fully instantiated types. You also need to enable the ForeignFunctionInterface
extension. As a result the toFFI
function, which should return an IO (FunPtr x)
, can't be polymorphic over function types. In other words, you'd need this:
foreign import ccall "wrapper"
mkIntFn :: (Int32 -> Int32) -> IO (FunPtr (Int32 -> Int32))
foreign import ccall "dynamic"
dynIntFn :: FunPtr (Int32 -> Int32) -> (Int32 -> Int32)
instance FFI (Int32 -> Int32) (FunPtr (Int32 -> Int32)) where
toFFI = mkIntFn
fromFFI = return . dynIntFn
freeFFI = freeHaskellFunPtr
for every different function type you want to marshal. You also need the FlexibleInstances
extension for this instance. There are a few restrictions imposed by the FFI: every type must be a marshallable foreign type, and the function return type must be either a marshallable foreign type or an IO action which returns a marshallable foreign type.
For non-marshallable types (e.g. Strings) you need something slightly more complex. First of all, since marshalling happens in IO you can only marshal functions that result in an IO action. If you want to marshal pure functions, e.g. (String -> String), you need to lift them to the form (String -> IO String).[2] Let's define two helpers:
wrapFn :: (FFI a ca, FFI b cb) => (a -> IO b) -> (ca -> IO cb)
wrapFn fn = fromFFI >=> fn >=> toFFI
unwrapFn :: (FFI a ca, FFI b cb) => (ca -> IO cb) -> (a -> IO b)
unwrapFn fn a = bracket (toFFI a) freeFFI (fn >=> fromFFI)
These convert the types of functions to the appropriate marshalled values, e.g. wrapStrFn :: (String -> IO String) -> (CString -> IO CString); wrapStrFn = wrapFn
. Note that unwrapFn
uses "Control.Exception.bracket" to ensure the resource is freed in case of exceptions. Ignoring this you could write unwrapFn fn = toFFI >=> fn >=> fromFFI
; see the similarity to wrapFn.
Now that we have these helpers we can start to write instances:
foreign import ccall "wrapper"
mkStrFn :: (CString -> IO CString) -> IO (FunPtr (CString -> IO CString))
foreign import ccall "dynamic"
dynStrFn :: FunPtr (CString -> IO CString) -> (CString -> IO CString)
instance FFI (String -> IO String) (FunPtr (CString -> IO CString)) where
toFFI = mkStrFn . wrapFn
fromFFI = return . unwrapFn . dynStrFn
freeFFI = freeHaskellFunPtr
As before, it's not possible to make these functions polymorphic, which leads to my biggest reservation about this system. It's a lot of overhead because you need to create separate wrappers and instances for each type of function. Unless you're doing a lot of marshalling of functions, I would seriously doubt it's worth the effort.
That's how you can marshal functions, but what if you want to make them available to calling code? This other process is exporting the function, and we've already developed most of what's necessary.
Exported functions must have marshallable types, just like FunPtr
s. We can simply re-use the wrapFn
to do this. To export a few functions all you need to do is wrap them with wrapFn
and export the wrapped versions:
f1 :: Int -> Int
f1 = (+2)
f2 :: String -> String
f2 = reverse
f3 :: String -> IO Int
f3 = return . length
foreign export ccall f1Wrapped :: CInt -> IO CInt
f1Wrapped = wrapFn (return . f1)
foreign export ccall f2Wrapped :: CString -> IO CString
f2Wrapped = wrapFn (return . f2)
foreign export ccall f3Wrapped :: CString -> IO CInt
f3Wrapped = wrapFn f3
Unfortunately this setup only works for single-argument functions. To support all functions, let's make another class:
class ExportFunction a b where
exportFunction :: a -> b
instance (FFI a ca, FFI b cb) => ExportFunction (a->b) (ca -> IO cb) where
exportFunction fn = (wrapFn (return . fn))
instance (FFI a ca, FFI b cb, FFI d cd) => ExportFunction (a->b->d) (ca->cb->IO cd) where
exportFunction fn = \ca cb -> do
a <- fromFFI ca
b <- fromFFI cb
toFFI $ fn a b
Now we can use exportFunction
for functions with 1 and 2 arguments:
f4 :: Int -> Int -> Int
f4 = (+)
f4Wrapped :: CInt -> CInt -> IO CInt
f4Wrapped = exportFunction f4
foreign export ccall f4Wrapped :: CInt -> CInt -> IO CInt
f3Wrapped2 = :: CString -> IO CInt
f3Wrapped2 = exportFunction f3
foreign export ccall f3Wrapped2 :: CString -> IO CInt
f3Wrapped2 = exportFunction f3
Now you just need to write more instances of ExportFunction
to automatically convert any function to the appropriate type for exporting. I think this is the best you can do without either either using some type of pre-processor or unsafePerformIO.
[1] Technically, I don't think there's any need for the "basic -> ffitype" fundep, so you could remove it to enable one basic type to map to multiple ffitypes. One reason to do so would be to map all sized ints to Integers, although the toFFI
implementations would be lossy.
[2] A slight simplification. You could marshal a function String -> String
to the FFI type of CString -> IO CString
. But now you can't convert the CString -> IO CString
function back to String -> String
because of the IO in the return type.
这篇关于在Haskell中为FFI调用自动转换类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!