Haskell 中 FFI 调用的类型自动转换
我定义了以下模块来帮助我进行 FFI 函数导出:
{-# 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 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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您可以利用涉及 FFI 的功能执行以下两件事:
1) 编组:这意味着将函数转换为可以通过 FFI 导出的类型。这是由
FunPtr
完成的。2) 导出:这意味着为非 Haskell 代码创建一种调用 Haskell 函数的方法。
您的 FFI 类有助于编组,首先我创建了一些如何编组函数的示例实例。
这是未经测试的,但它可以编译,我希望它能工作。首先,让我们稍微改变一下类:
这表示给定“basic”或“ffitype”的类型,另一个是固定的[1]。这意味着不再可能将两个不同的值编组为同一类型,例如,您不能再同时拥有这两个值。
这样做的原因是
freeFFI
无法按照您定义的方式使用;无法仅从 ffitype 中确定选择哪个实例。或者,您可以将类型更改为 freeFFI :: ffitype ->基本-> IO(),或者(更好?)freeFFI::ffitype -> IO 基本
。那么你就根本不需要fundeps了。分配 FunPtr 的唯一方法是使用“foreign import”语句,该语句仅适用于完全实例化的类型。您还需要启用
ForeignFunctionInterface
扩展。因此,应返回 IO (FunPtr x) 的toFFI
函数不能在函数类型上实现多态。换句话说,您需要这个:对于您想要封送的每种不同的函数类型。您还需要此实例的
FlexibleInstances
扩展。 FFI 施加了一些限制:每种类型都必须是可编组外部类型,并且函数返回类型必须是可编组外部类型或返回可编组外部类型的 IO 操作。对于不可编组类型(例如字符串),您需要稍微复杂一些的东西。首先,由于编组发生在 IO 中,因此您只能编组导致 IO 操作的函数。
如果你想编组纯函数,例如 (String -> String),你需要将它们提升为 (String -> IO String) 的形式。[2]让我们定义两个帮助器:
它们将函数的类型转换为适当的编组值,例如
wrapStrFn :: (String -> IO String) -> (CString -> IO CString);包装StrFn = 包装Fn
。请注意,unwrapFn
使用“Control.Exception.bracket”来确保在发生异常时释放资源。忽略这一点,您可以编写unwrapFn fn = toFFI >=> fn >=>来自FFI
;看看与wrapFn 的相似之处。现在我们有了这些助手,我们可以开始编写实例了:
和以前一样,不可能使这些函数具有多态性,这导致了我对这个系统最大的保留。这是很大的开销,因为您需要为每种类型的函数创建单独的包装器和实例。除非您要进行大量的功能编组,否则我会严重怀疑这是否值得付出努力。
这就是编组函数的方式,但是如果您想让它们可用于调用代码怎么办?另一个过程是导出功能,我们已经开发了大部分必要的功能。
导出的函数必须具有可编组类型,就像
FunPtr
一样。我们可以简单地重新使用wrapFn
来做到这一点。要导出一些函数,您需要做的就是用wrapFn
包装它们并导出包装的版本:不幸的是,此设置仅适用于单参数函数。为了支持所有函数,让我们创建另一个类:
现在我们可以将
exportFunction
用于具有 1 和 2 个参数的函数:现在您只需要编写更多
ExportFunction
实例即可自动转换将任何函数转换为适当的类型以进行导出。我认为这是在不使用某种类型的预处理器或 unsafePerformIO 的情况下可以做的最好的事情。[1] 从技术上讲,我认为没有必要使用“basic -> ffitype”fundep,因此您可以删除它以使一种基本类型能够映射到多个 ffitype。这样做的原因之一是将所有大小的整数映射到整数,尽管
toFFI
实现将会有损。[2] 稍微简化。您可以编组一个函数
String -> String
到CString -> 的 FFI 类型IO CString
。但现在您无法转换CString -> IO CString
函数返回String -> String
因为IO中的返回类型。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:
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
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 tofreeFFI :: 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 thetoFFI
function, which should return anIO (FunPtr x)
, can't be polymorphic over function types. In other words, you'd need this: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:
These convert the types of functions to the appropriate marshalled values, e.g.
wrapStrFn :: (String -> IO String) -> (CString -> IO CString); wrapStrFn = wrapFn
. Note thatunwrapFn
uses "Control.Exception.bracket" to ensure the resource is freed in case of exceptions. Ignoring this you could writeunwrapFn fn = toFFI >=> fn >=> fromFFI
; see the similarity to wrapFn.Now that we have these helpers we can start to write instances:
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 thewrapFn
to do this. To export a few functions all you need to do is wrap them withwrapFn
and export the wrapped versions:Unfortunately this setup only works for single-argument functions. To support all functions, let's make another class:
Now we can use
exportFunction
for functions with 1 and 2 arguments: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 ofCString -> IO CString
. But now you can't convert theCString -> IO CString
function back toString -> String
because of the IO in the return type.