2013-01-03 9 views
11

私は、C言語のライブラリにFFIモジュールを作成しています。これは、1回限りの非リエントラント関数を呼び出す前に呼び出されます。この呼び出しは等式ですが、ステートフルなので、すべてのHaskell呼び出しで呼び出すことができます。しかし、それは遅いですし、非リエントラントのために、それは衝突を引き起こす可能性があります。unsafePerformIOとFFIライブラリの初期化

これで、unsafePerformIOを使用するのは適切なタイミングですか?安全でないIORefまたはMVarにBoolをラップして、後続の呼び出し(グローバルで隠されたIORef状態がFalseの呼び出し)を無視して、これらの初期化呼び出しを冪等価にすることができます。

そうでない場合、これを行う正しい方法は何ですか?

答えて

11

マシンを初期化したという証拠として、一度初期化して偽造不可能なトークンを提供するアプローチが好きです。

だからあなたの証拠は、次のようになります。あなたは抽象的にエクスポート

data Token = Token 

あなたの初期化関数は、この証拠を返すことができます。

init :: IO Token 

さて、あなたはあなたのAPIにその証明を渡す必要があります:

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

など

あなたは今モナド、またはいくつかのより高次の初期化環境にしてこのようなものを包むことができますそれをよりきれいにしますが、それは基本的な考え方です。

隠し状態を使用してCライブラリを初期化する際の問題は、ライブラリへのアクセスを並列化できない、またはGHCiでコンパイル済みコードとバイトコードを混在させて、リンカーエラーで失敗します)。

+2

1つの選択肢は、メインの周り 'withX'ラッパーです。これは静的な保護者を与えません、私はちょうどそこに優先順位(ネットワークパッケージからの 'withSocketsDo')があ​​ると言っています。 –

+1

ああ、良い点。 'withToken $ \ t - >'より簡単ですが、保証はありません。 –

+1

ああ、優秀!これははるかに良い解決策です。私は、グローバルステートがマルチスレッドとどのように相互作用するか心配していました(unsafeされたMVarスレッドローカル、ランタイムローカルですか?)。これはまた、暗黙的で隠れているのではなく、Haskellランタイムで初期化の失敗をローカライズ可能にします。 –

2

私は現在、evaluateに基づいて/代わりwithSocketsDoby Neil Mitchellのためのいくつかの新しいトリックis suggestedは、(「結果のIOアクションが実行されたときに、弱い頭部正規形に評価されるために、その引数を強制します。」)ことに注意したいと思います: withSocketsDoを呼び出すための要件を除去する

withSocketsDo act = do evaluate withSocketsInit; act 

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

私のアプローチは、それが必要になることがありますどこにでもそれを振りかける、それは非常に安い作る にしました。

は必ずしもこれは美しいアイデアではありません...使用を見ている

(もhis answerがライブラリにこの更新プログラムを発表参照してください。)