Haskellでのリソース管理
C++にあってCにないものの一つ、クラスのコンストラクタとデストラクタがあります。これを使えば、スマートポインターなどといった、リソース管理を半自動化することができるような便利なクラスを定義できるようになるわけです。
プログラムを書いていると、色々な局面で、使い終わったときに何かしなくてはいけないものを受け取ることがありますよね...例えば、ファイルハンドルとか、Win32で色々書いてると、ほかにもデバイスコンテキストだとか、GDIの描画オブジェクトとか、プロセス、スレッドのハンドルとか…
C++だったら、
templateclass SmartH { public: SmartH(H h){m_h = h;} ~SmartH() { if (m_h != null) CloseHandle(m_h); } // on and on... private: H m_h; };
みたいなクラスがあれば、
main() { SmartH<HANDLE> h(OpenFile("foobar.cpp")); //do stuff }
といった感じで、明示的にOpenFileで受け取ったファイルハンドルを開放するCloseHandleを呼ばなくても、main関数が実行終了する際に、h変数のデストラクタが自動的に呼び出されるので、CloseHandleを呼ぶのを忘れちゃったといったような人為的なミスを防ぐのに効果的なわけです。実際に使うためにはもっとちゃんとした実装をしなくちゃいけないし、そうゆう実装はもうちゃんとしたものがあるのだろうと思うのですが、(知らない...泣。知ってたら教えて下さい。後学のために...)
これに似たようなことをHaskellでやろうとおもったら、どうやればいいでしょう...HaskellのクラスはC++のものとはちょっとちがうし、そもそも、コンストラクタとか、デストラクタとか、そんなものは全然ないわけです。
できないのかなーとか思っていたら、System.IO.withFileという関数を見つけました。これの型はこんな感じです:
withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
これとそっくりの機能を提供する関数が:
openFile :: FilePath -> IOMode -> IO Handle
それで、違いは何かというと、openFileはファイルを開いて、そのハンドルを返すのに対して、withFileはファイルを開くのですが、開いてそのまま帰らずに、そのファイルハンドルをパラメタとして渡された(Handle -> IO r)な関数に渡して、その関数の実行が終わったときにhCloseでハンドルを開放してから、IO rな値を返すわけです。
たしかに、これなら割ときれいにかけますね...でも、ファイルの中身に対する操作をいちいち別の関数にパッケージしなくちゃいけないのは、なんだかうざったい気もします…さらに、パラメタとして渡す関数の中でそのファイルに対する処理を全て終わらせなくちゃいけないのも、ちょっと窮屈かもしれません…
C++の場合は、さっきの例のようなSmartH
withFileの場合は関数がリソース管理の単位というのはどうも逃れようがないような気がします...もしリソース管理がされているオブジェクトを3つ使うコードを書かなくてはいけないときは、関数を3段ネストしなくてはいけないというのは、ちょっと悲しいですね...かなーと、想像したのですが、aljeeさんのコメントを見て、関数のネストは、そんなに悲しくないと思いました...
次に見てみたのは:
System.IO.readFile :: FilePath -> IO String
です。Hoogleの解説によると、この関数はread lazilyだそうです。中身を見てみると、結構単純でした:
readFile :: FilePath -> IO String
readFile name = openFile name ReadMode >>= hGetContents
どうやら、肝は、hGetContentsにあるようで、説明によると、hGetContentsはハンドルを準クローズド状態にするんだそうです。いったいなんだそりゃ?この辺、うまくリソース管理ができないという現実がなんだかにじみ出ているような気もしますね...まぁ、シーケンシャルなファイルアクセスならば便利なのでしょうけど...応用が利かない感は否めません...
リストなどの言語仕様の範疇のリソースは高度なガベージコレクションが働いているわけですが、ファイルハンドルなんかは、そうゆう扱いをするわけにはいかんのですかねぇ…
ではでは