2006-08-26再入門Haskell(前編)Haskell(私はあんまり広く情報収集したりしないので、ここに書く内容はとうによく知られた問題なのかもしれないが、あしからず...)プログラムの実行において、何らかの外部的な情報(つまりIOを介して得られる情報)をそのプログラム全体から参照したいケースというのがある。典型的な例がプログラムのコンフィギュレーションで、たとえば、設定ファイルから設定値を読み込み、そのパラメータを用いて実行する、あるいはコマンドライン引数にしたがって動作する、またあるいは、環境変数、レジストリ、などを参照する場合である。このような設定はプログラム中のどこで参照されるかあらかじめ分かっているとも限らないので、どこからでも気軽に参照できるのが望ましい。実際のところ、一般的な言語ではこのような設定はグローバル変数にしてしまうか、あるいはグローバル変数を嫌うならシングルトンな設定クラスを作るか、そうでないクラスを作ってそれを使う可能性のあるクラスに持ちまわさせるなどでも良いが、いずれにせよ、利用されると考えられるよりかなり大きなコードの範囲で参照可能な変数として置くのが普通である。ところで、Haskellは参照透明な言語なので、グローバルなところに変数は置けない。参照透明のためにいわゆるmutableな変数は置けない。関数の引数、ローカル変数の持つものしかない。はじめに、次のようなプログラムを考える。hサーバのアドレスとポート番号を指定されて、そこに接続してなにかデータを取ってくるというプログラムのつもりである。ここでは、アドレスとポート番号はとりあえず定数にしてしまって、ひとまず動くものを作ったというシナリオを想定していただきたい。そのような理由で、後々この部分は可変にしたい。定数の場合、他の言語と同じく、このようにグローバルなところに変数を置くことにより簡単に実装が出来る。これを可変にしようとするととたんにこのままでは立ち行かなくなってしまう。Haskellでこれを行うのには大体三つほど方法が考えられる。一つ目、必要な箇所全てにパラメータを渡してやる方法。h設定は何らかのIO処理をして取得するものとする。はじめに設定を取得して、それを用いる全ての関数に渡してやることによって実現する。安易な方法だが、これでは大いに問題があって、このサンプルでは関数が2個しかないが、もっとたくさんあった場合、それら全てに設定を伝播してやる必要があるので、とんでもなく面倒になってしまう。そもそも、そのようなものを関数の引数として渡すのはなんだかおかしいようにも感じられる。プログラムを少し変形すると、同じ考え方で各関数の引数に受け渡すことなく実装することも出来る。hこのように一つの大きなクロージャにしてしまうと一つ一つに渡す必要はなくなる。全体をインデントしなければいけない。設定を参照する関数全てをまとめないといけないということからモジュラリティにかなり難がある。一つの問題点として、この方式では実行中に設定項目をプログラム自身によって書き換えるということに対応できない。GUIアプリケーションなどは設定画面によって実行中に設定を変えるというのは珍しくない。二つ目の方法として、Stateモナドを使うものが考えられる。実行中の書き換えにも対応できる。この方法の欠点は、設定値がモナドになるということだろうか。何をする前にもモナドを外す必要がある。設定値の持ち回りのために計算がモナドで覆われるので、さらに別のモナドを使いたい場合、よくあるものとしてIOを行いたい場合はモナド合成を行ったうえで、各IO処理をリフトさせる必要が出てくる。これが意外と煩雑でたくさんコードを書いているとよく書き忘れる。書き忘れたときのGHCのエラーメッセージが大抵びっくりするほど分かりずらい。間違った型のモナドとして型推論が進んでしまって、まったく別の場所でエラーが表示されるからなのだが...。それとは別に、この方法でも設定を使うコードを特定のモナドにする必要があるので、モジュラリティの点からもあまり良いとは言えないように思える。これらいずれも元からコードがかなり変わる上にモジュラリティに難がある。はじめの例のように、設定固定でプロトタイプを作ってから可変にするのはなかなか大変である。そこで、三つ目の方法として、元のプログラムをほとんど変えずに設定に対応させるものを考える。Haskellは参照透明ではあるが、それゆえに、設定がリードオンリーなら、割と簡単に実装することが出来る。hunsafePerformIOの出番というわけである。この場合、loadConfigは設定値の初回参照時に実行され、server,portNumberはその時に決定される。グラフ簡約なので、一度簡約されるともうloadConfigが実行されることは無い。それゆえに、このような実装で全く問題なく動作する。同じようなことをもう一段行えばよい。hコマンドライン引数からファイル名を指定する例である。ファイル名の実体としてはIORefで持っておき、ファイル名の初回参照時にreadIORefで取り出す。以降の計算は全てうまく行われる。このように、リードオンリーの場合においてはかなり理想的な実装ができる。設定の書き換えが必要になった場合はこの方法は使えない。次回、設定の書き換えありで(割と)うまく動作する実装を示す。その3前回の続きでB1からになります。writeup月別アーカイブはてなブログをはじめよう!tanakhさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。