我有Haskell代码需要与C库接口,有点像这样:
// MyObject.h
typedef struct MyObject *MyObject;
MyObject newMyObject(void);
void myObjectDoStuff(MyObject myObject);
//...
void freeMyObject(MyObject myObject);
Run Code Online (Sandbox Code Playgroud)
原始的FFI代码将所有这些函数包装为纯函数unsafePerformIO.这导致了错误和不一致,因为操作的顺序是不确定的.
我正在寻找的是一种处理Haskell中的对象而不诉诸于所有内容的一般方法IO.什么是好的是我可以做的事情,如:
myPureFunction :: String -> Int
-- create object, call methods, call destructor, return results
Run Code Online (Sandbox Code Playgroud)
有没有一个很好的方法来实现这一目标?
我们的想法是不断传递每个组件的接力棒,以强制按顺序评估每个组件.这基本上就是状态monad(IO实际上是一个奇怪的状态monad.有点).
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import Control.Monad.State
data Baton = Baton -- Hide the constructor!
newtype CLib a = CLib {runCLib :: State Baton a} deriving Monad
Run Code Online (Sandbox Code Playgroud)
然后你就把操作串起来了.将它们注入CLibmonad将意味着它们被排序.从本质上讲,你可以IO以一种更加不安全的方式伪装自己,因为你可以逃脱.
然后,你必须确保你添加construct和destruct全部结束CLib链.这可以通过导出类似的函数轻松完成
clib :: CLib a -> a
clib m = runCLib $ construct >> m >> destruct
Run Code Online (Sandbox Code Playgroud)
跳过的最后一个大箍是确保当你unsafePerformIO进入时construct,它实际上得到了评估.
坦率地说,这一切都是毫无意义的,因为它已经存在,战斗证明了IO.而不是整个精心制作的过程,只是如何
construct :: IO Object
destruct :: IO ()
runClib :: (Object -> IO a) -> a
runClib = unsafePerformIO $ construct >>= m >> destruct
Run Code Online (Sandbox Code Playgroud)
如果您不想使用该名称IO:
newtype CLib a = {runCLib :: IO a} deriving (Functor, Applicative, Monad)
Run Code Online (Sandbox Code Playgroud)
我的最终解决方案。它可能有一些我没有考虑到的微妙错误,但它是迄今为止唯一满足所有原始标准的解决方案:
不幸的是,实现有点复杂。
例如
// Stack.h
typedef struct Stack *Stack;
Stack newStack(void);
void pushStack(Stack, int);
int popStack(Stack);
void freeStack(Stack);
Run Code Online (Sandbox Code Playgroud)
c2hs 文件:
// Stack.h
typedef struct Stack *Stack;
Stack newStack(void);
void pushStack(Stack, int);
int popStack(Stack);
void freeStack(Stack);
Run Code Online (Sandbox Code Playgroud)
测试程序:
{-# LANGUAGE ForeignFunctionInterface, GeneralizedNewtypeDeriving #-}
module CStack(StackEnv(), runStack, pushStack, popStack) where
import Foreign.C.Types
import Foreign.Ptr
import Foreign.ForeignPtr
import qualified Foreign.Marshal.Unsafe
import qualified Control.Monad.Reader
#include "Stack.h"
{#pointer Stack foreign newtype#}
newtype StackEnv a = StackEnv
(Control.Monad.Reader.ReaderT (Ptr Stack) IO a)
deriving (Functor, Monad)
runStack :: StackEnv a -> a
runStack (StackEnv (Control.Monad.Reader.ReaderT m))
= Foreign.Marshal.Unsafe.unsafeLocalState $ do
s <- {#call unsafe newStack#}
result <- m s
{#call unsafe freeStack#} s
return result
pushStack :: Int -> StackEnv ()
pushStack x = StackEnv . Control.Monad.Reader.ReaderT $
flip {#call unsafe pushStack as _pushStack#} (fromIntegral x)
popStack :: StackEnv Int
popStack = StackEnv . Control.Monad.Reader.ReaderT $
fmap fromIntegral . {#call unsafe popStack as _popStack#}
Run Code Online (Sandbox Code Playgroud)
建造:
-- Main.hs
module Main where
import qualified CStack
main :: IO ()
main = print $ CStack.runStack x where
x :: CStack.StackEnv Int
x = pushStack 42 >> popStack
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
726 次 |
| 最近记录: |