a wandering wolf

Does a wandering wolf dreams of a wondering, sometimes programming sheep?

このエントリーをはてなブックマークに追加

IDisposable な Option 値を扱う

昨夜、こんな話題がありました。

それに対する反応がこちら。

このあたりをさらっと見ていこうと思ったのが、今回のトピックです。

パターンマッチしてみる

パターンマッチで Dispose されるようなコードを書いてみます。

まずは、指定したパスのファイルを開いて Option<FileStream> を返すような関数を定義します。

let tryOpen path =
    if File.Exists path then
        Some(new FileStream(path, FileMode.Open))
    else
        None

(他にも考慮すべきケースはありますが、最小ケースで考えます)

この関数に対し、パターンマッチと use 束縛を単純に組み合わせたコードがこちら。

let patternMatching path =
    match (tryOpen path) with
    | Some (fs) ->
        use disposable = fs
        use sr = new StreamReader(disposable)
        sr.ReadToEnd()
    | None -> "Not found."

tryOpen で取得できた FileStream は、ちゃんと Dispose されます。

でも、何でしょうね。この「いやいや、もっとお前の全力を見せてみろよ」感。

use! が使える Option ビルダークラスを作る

コンピュテーション式で use! を使えるようにして対応する、という別案を考察してみます。

use!let!use が使える状態にしておく必要があります(詳しくはコンピュテーション式の解説記事をいろいろ読んでみましょう)。

type DisposableOptionBuilder () =
    member __.Bind(x, f) =
        match x with
        | Some (v) -> f v
        | None     -> None
    member __.Return(x) = Some(x)
    member __.Using(x: #IDisposable, f) =
        try
            f x
        finally
            match x with
            | null -> ()
            | _ -> x.Dispose()

let option = DisposableOptionBuilder()

このビルダークラスがある状態で、以下のような関数を書いてみます。

let useBang path =
    let result = option {
        use! fs = tryOpen path
        use sr = new StreamReader(fs)
        return sr.ReadToEnd()
    }
    match result with
    | Some (s) -> s
    | None     -> "Not found."

こちらもちゃんと Dispose されました。

ただ、これくらいのコードに対しては、少しやり過ぎ感もあります。

結論

個人的には use! 推しですが、ほんの数か所程度だったらパターンマッチの中で use 束縛しても良いかなと思います。ケースバイケースで。