问题 unsafePerformIO和FFI库初始化


我正在为C中的库创建一个FFI模块,它希望在其他任何东西之前调用一次非重入函数。这个调用是幂等的,但是有状态的,所以我可以在每个Haskell调用中调用它。但它很慢并且由于不可重入而可能导致冲突。

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

如果没有,这样做的正确方法是什么?


8752
2018-01-03 18:46


起源



答案:


我更喜欢初始化一次并提供不可伪造的令牌作为您初始化机器的证据。

所以你的证据是:

data Token = Token

你抽象地导出。

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

init :: IO Token

现在,您需要将该证明传递给您的API:

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

等等

现在,您可以使用monad或更高阶的初始化环境来包装这些内容,以使其更清晰,但这是基本的想法。

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


11
2018-01-03 19:02



可以使用的一种替代方案是 withX 主要包装。这没有静态的guarentees,我只是说有优先权(例如。 withSocketsDo 来自网络包)。 - Thomas M. DuBuisson
啊,是的,好的一点。比简单 withToken $ \t ->,但没有保证。 - Don Stewart
啊,太棒了!这是一个更好的解决方案。我一直担心全局状态如何与多线程交互(是一个不安全的MVar线程本地,运行时本地?)。这也使得初始化失败在Haskell运行时可以本地化,而不仅仅是隐式和隐藏。 - J. Abrahamson
我一直在考虑是否“现在用monad包装这些东西”。从一方面来看,它看起来在概念上很好:在新的monad中,你有这种特殊的monadic类型的动作,只有在初始化之后才能调用。为了将它们与正常的IO动作交错,我必须为此创建一个monad变换器(并解除IO动作)。但后来我想知道:这真的值得吗?!人们可能希望monad命令行动(修改初始化状态的不同顺序给出不同的结果),但是 IO已经强制下令。它 清除 需要智能单子! - imz -- Ivan Zakharyaschev
至于创建一个monad(“现在用monad包装这个东西”)用于依赖于幂等(有状态)先前动作的动作:我只是 读 一个可以用于更普遍问题的想法:吐出许多 createDirectory,然后这个动作列表减少了 nub 并投入IO。如果只有一个未参数化的东西必须初始化,你可以避免开销 nub - imz -- Ivan Zakharyaschev


答案:


我更喜欢初始化一次并提供不可伪造的令牌作为您初始化机器的证据。

所以你的证据是:

data Token = Token

你抽象地导出。

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

init :: IO Token

现在,您需要将该证明传递给您的API:

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

等等

现在,您可以使用monad或更高阶的初始化环境来包装这些内容,以使其更清晰,但这是基本的想法。

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


11
2018-01-03 19:02



可以使用的一种替代方案是 withX 主要包装。这没有静态的guarentees,我只是说有优先权(例如。 withSocketsDo 来自网络包)。 - Thomas M. DuBuisson
啊,是的,好的一点。比简单 withToken $ \t ->,但没有保证。 - Don Stewart
啊,太棒了!这是一个更好的解决方案。我一直担心全局状态如何与多线程交互(是一个不安全的MVar线程本地,运行时本地?)。这也使得初始化失败在Haskell运行时可以本地化,而不仅仅是隐式和隐藏。 - J. Abrahamson
我一直在考虑是否“现在用monad包装这些东西”。从一方面来看,它看起来在概念上很好:在新的monad中,你有这种特殊的monadic类型的动作,只有在初始化之后才能调用。为了将它们与正常的IO动作交错,我必须为此创建一个monad变换器(并解除IO动作)。但后来我想知道:这真的值得吗?!人们可能希望monad命令行动(修改初始化状态的不同顺序给出不同的结果),但是 IO已经强制下令。它 清除 需要智能单子! - imz -- Ivan Zakharyaschev
至于创建一个monad(“现在用monad包装这个东西”)用于依赖于幂等(有状态)先前动作的动作:我只是 读 一个可以用于更普遍问题的想法:吐出许多 createDirectory,然后这个动作列表减少了 nub 并投入IO。如果只有一个未参数化的东西必须初始化,你可以避免开销 nub - imz -- Ivan Zakharyaschev


我想指出目前一些新技巧 有人建议 为/而不是 withSocketsDo  作者:尼尔米切尔, 基于 evaluate (“当执行生成的IO操作时,强制将其参数计算为弱头正常形式。”):

withSocketsDo act = do evaluate withSocketsInit; act 

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

我删除调用withSocketsDo的要求的方法是   使它非常便宜,然后将它洒在可能需要的任何地方。

不一定这是一个美丽的想法......

(也可以看看 他的回答 在库中宣布此更新。)


2
2018-02-26 21:20