我正在创建一个FFI模块,连接到一个在C中的库,该库要求调用一个一次性、不可重入的函数,在任何其他操作之前。这个调用是幂等的,但具有状态,因此我可以在每个Haskell调用中都调用它。但是这样做很慢,并且由于不可重入性可能会导致冲突。
那么现在是使用unsafePerformIO的正确时间吗?我可以在一个不安全的IORef或MVar中包装一个布尔值,通过忽略后续调用(全局隐藏的IORef状态为False的调用)使这些初始化调用变得幂等。
如果不是,那么正确的方法是什么?
我正在创建一个FFI模块,连接到一个在C中的库,该库要求调用一个一次性、不可重入的函数,在任何其他操作之前。这个调用是幂等的,但具有状态,因此我可以在每个Haskell调用中都调用它。但是这样做很慢,并且由于不可重入性可能会导致冲突。
那么现在是使用unsafePerformIO的正确时间吗?我可以在一个不安全的IORef或MVar中包装一个布尔值,通过忽略后续调用(全局隐藏的IORef状态为False的调用)使这些初始化调用变得幂等。
如果不是,那么正确的方法是什么?
我更喜欢初始化一次并提供一个无法伪造的令牌作为证明你已经初始化了机器的方法。
因此,您的证据将是:
data Token = Token
抽象地导出的内容。
然后,您的初始化函数可以返回此证据。
init :: IO Token
现在,您需要将该证明传递给您的API:bar :: Token -> IO Int
bar !tok = c_call_bar
等等,你可以使用单子或某些高阶初始化环境来封装这个东西,使其更加简洁,但这就是基本思路。
使用隐藏状态初始化C库的问题在于,要么不能并行访问库,要么在GHCi中出现问题,混合编译和字节码,并加载两个不同版本的C库(这将导致链接器错误)。
我想指出,目前有一些新的技巧被Neil Mitchell建议用于替代withSocketsDo
,这是基于evaluate
(“强制执行其参数到弱头正常形式,当执行产生IO操作时。”):
withSocketsDo act = do evaluate withSocketsInit; act
{-# NOINLINE withSocketsInit #-}
withSocketsInit = unsafePerformIO $ do
initWinsock
termWinsock
我去除调用withSocketsDo的方法是让它变得非常便宜,然后在可能需要的地方随处使用。
这并不一定是一个好主意...
(另请参见他在库中宣布此更新的答案。)
withX
包装器。这并不提供静态保证,我只是说有先例(例如来自网络包的withSocketsDo
)。 - Thomas M. DuBuissonIO
已经强制排序。它消除了智能单子的需求! - imz -- Ivan ZakharyaschevcreateDirectory
,然后使用nub
减少此操作列表并将其馈送到IO中。如果只有一个未参数化的要初始化的事物,则可以避免nub
的开销。 - imz -- Ivan Zakharyaschev