2013-01-03 66 views
11

我正在创建一个FFI模块到C库中,它希望在其他任何事情之前调用一次性的非重入函数。这个调用是幂等的,但是有状态的,所以我可以在每个Haskell调用中调用它。但速度很慢,由于非重入可能会导致冲突。unsafePerformIO和FFI库初始化

那么这是使用unsafePerformIO的正确时机吗?我可以将Bool包装在不安全的IORef或MVar中,通过忽略后续调用(全局隐藏的IORef状态为False的调用)来使这些初始化调用为幂等。

如果没有,那么做到这一点的正确方法是什么?

回答

11

我更喜欢初始化一次的方法,并提供一个不可伪造的标记作为您已初始化机器的证据。

所以,你的证据是:

data Token = Token 

您导出抽象。

然后你的初始化函数可以返回这个证据。

init :: IO Token 

现在,你需要的是证明传递到您的API:

bar :: Token -> IO Int 
bar !tok = c_call_bar 

您现在可以换这个东西了一个单子,或者一些更高阶的初始化环境使它更清洁,但这是基本的想法。

使用隐藏状态初始化C库的问题是,您最终无法并行访问库,或者在GHCi中遇到问题,将编译和字节码混合,加载了两个不同版本的C库(这将失败并出现链接错误)。

+2

一个选择,已经看到使用是围绕主的'withX'包装。这没有给出任何静态保证,我只是说它有优先权(例如,来自网络包中的“带有套接字”)。 –

+1

啊,是的,好点。比'withToken $ \ t - >'简单,但没有保证。 –

+1

啊,非常棒!这是一个更好的解决方案。我一直担心全局状态如何与多线程交互(是一个不安全的MVar线程本地运行时本地?)。这也会使初始化失败在Haskell运行时中定位,而不仅仅是隐式和隐藏。 –

2

我想指出,当前为/,而不是withSocketsDoby Neil Mitchell一些新的技巧is suggested,基于evaluate(“强制执行所产生的IO操作时它的参数进行评估,以微弱的头部正常形态。”):

withSocketsDo act = do evaluate withSocketsInit; act 

{-# NOINLINE withSocketsInit #-} 
withSocketsInit = unsafePerformIO $ do 
    initWinsock 
    termWinsock 

我的办法,消除要求调用withSocketsDo是 使它很便宜,然后到处洒它可能是必要的。

不一定,这是一个美好的理想......

(参见his answer宣布在图书馆此更新。)