2011-12-26 15 views
7

F#コードのこのスニペット次の警告でF#の再帰オブジェクト?

let rec reformat = new EventHandler(fun _ _ -> 
     b.TextChanged.RemoveHandler reformat 
     b |> ScrollParser.rewrite_contents_of_rtb 
     b.TextChanged.AddHandler reformat 
     ) 
    b.TextChanged.AddHandler reformat 

結果:

traynote.fs(62,41):警告FS0040:この及び他の再帰的な参照オブジェクトへの(S)であります遅延参照の使用による実行時の初期化の健全性をチェックします。これは、再帰関数ではなく、1つ以上の再帰オブジェクトを定義するためです。この警告は、 '#nowarn "40"'または '--nowarn:40'を使用することで抑止できます。

この警告を回避するためにコードを書き直す方法はありますか?それとも、F#で再帰オブジェクトを持つためのコーシャーな方法はありませんか?

答えて

14

あなたのコードは、再帰オブジェクトを構築するための完璧な方法です。コンパイラは、参照が初期化される前にアクセスされないことを保証できないため、警告が出されます(実行時エラーが発生します)。ただし、EventHandlerが建設中に提供されたラムダ関数を呼び出さないことが分かっている場合は、警告を無視しても問題ありません。

は、警告が実際に問題を示している例を与えるためには、次のコードを試すことができます。

type Evil(f) = 
    let n = f() 
    member x.N = n + 1 

let rec e = Evil(fun() -> 
    printfn "%d" (e:Evil).N; 1) 

Evilクラスは、コンストラクタ内の関数を取り、建設の間にそれを呼び出します。その結果、ラムダ関数の再帰参照は、値に設定される前にeにアクセスしようとします(実行時エラーが発生します)。しかし、特にイベントハンドラを扱う場合、これは問題ではありません(また、再帰オブジェクトを正しく使用しているときには警告が表示されます)。

警告を取り除きたい場合は、明示的なref値を使用してnullを使用してコードを書き換えることができますが、その後、あなただけの警告なしと醜いコードで、ランタイムエラーの同じ危険になるだろう:

let foo (evt:IEvent<_, _>) = 
    let eh = ref null 
    eh := new EventHandler(fun _ _ -> 
    evt.RemoveHandler(!eh)) 
    evt.AddHandler(!eh)