Ten*_*ner 5 haskell ffi typeclass
我已经定义了以下模块来帮助我进行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
Run Code Online (Sandbox Code Playgroud)
我正在努力实现函数的实例.有人能帮我吗?
对于涉及FFI的函数,您可以做两件事:1)编组:这意味着将函数转换为可以通过FFI导出的类型.这完成了FunPtr
.2)导出:这意味着为非Haskell代码创建一种调用Haskell函数的方法.
您的FFI类有助于编组,首先我创建了一些如何编组函数的示例实例.
这是未经测试的,但它编译,我希望它会工作.首先,让我们稍微改变一下这个类:
class FFI basic ffitype | basic -> ffitype, ffitype -> basic where
toFFI :: basic -> IO ffitype
fromFFI :: ffitype -> IO basic
freeFFI :: ffitype -> IO ()
Run Code Online (Sandbox Code Playgroud)
这说明,给定"基本"或"ffitype"的类型,另一个是固定的[1].这意味着不再可能将两个不同的值编组为同一类型,例如,您不能再同时使用这两个值
instance FFI Int CInt where
instance FFI Int32 CInt where
Run Code Online (Sandbox Code Playgroud)
原因是因为freeFFI
不能像你定义的那样使用它; 没有办法确定从ffitype中选择哪个实例.或者,您可以将类型更改为freeFFI :: ffitype -> basic -> IO ()
,或(更好?)freeFFI :: ffitype -> IO basic
.那么你根本不需要玩弄.
分配FunPtr的唯一方法是使用"外部导入"语句,该语句仅适用于完全实例化的类型.您还需要启用ForeignFunctionInterface
扩展程序.因此toFFI
,应该返回的IO (FunPtr x)
函数不能是函数类型的多态.换句话说,你需要这个:
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
Run Code Online (Sandbox Code Playgroud)
对于您想要编组的每种不同的函数类型.您还需要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 -> IO cb) -> (a -> IO b)
unwrapFn fn a = bracket (toFFI a) freeFFI (fn >=> fromFFI)
Run Code Online (Sandbox Code Playgroud)
这些将函数类型转换为适当的编组值,例如wrapStrFn :: (String -> IO String) -> (CString -> IO CString); wrapStrFn = wrapFn
.请注意,unwrapFn
使用"Control.Exception.bracket"可确保在发生异常时释放资源.忽略这一点,你可以写unwrapFn fn = toFFI >=> fn >=> fromFFI
; 看看wrapFn的相似之处.
现在我们有了这些帮助器,我们可以开始编写实例了:
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
Run Code Online (Sandbox Code Playgroud)
和以前一样,不可能使这些函数具有多态性,这导致我对这个系统的最大保留.这是一个很大的开销,因为你需要为每种类型的函数创建单独的包装器和实例.除非你正在进行大量的功能编组,否则我会非常怀疑它是值得的.
这就是你如何编组函数,但如果你想让它们可用于调用代码呢?另一个过程是导出函数,我们已经开发了大部分必要的东西.
导出的函数必须具有可编组类型,就像FunPtr
s 一样.我们可以简单地重复使用它wrapFn
来做到这一点.要导出一些函数,您需要做的就是用它们包装wrapFn
并导出包装的版本:
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
Run Code Online (Sandbox Code Playgroud)
不幸的是,这种设置仅适用于单参数函数.为了支持所有功能,让我们创建另一个类:
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
Run Code Online (Sandbox Code Playgroud)
现在我们可以使用exportFunction
带有1和2参数的函数:
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
Run Code Online (Sandbox Code Playgroud)
现在您只需编写更多实例ExportFunction
来自动将任何函数转换为适当的类型以进行导出.我认为如果不使用某种类型的预处理器或unsafePerformIO,这是最好的.
[1]从技术上讲,我认为不需要"basic - > ffitype"fundep,所以你可以删除它以使一个基本类型映射到多个ffitypes.这样做的一个原因是将所有大小的整数映射到整数,尽管toFFI
实现将是有损的.
[2]略有简化.您可以将函数编组为String -> String
FFI类型CString -> IO CString
.但是现在由于返回类型中的IO,您无法将CString -> IO CString
函数转换回String -> String
.
归档时间: |
|
查看次数: |
888 次 |
最近记录: |