参照される回数が多く、書き換えられる事が比較的少ないデータはしばしば大域変数(グローバル変数)として定義される事があります。
Gaucheには大域変数の代わりに使えるパラメータと呼ばれる機能をサポートしています。パラメータはgauche.parameterライブラリで実現されています。さらにgauche.parameterライブラリには、パラメータを使うための便利な機能がいくつか用意されています。
パラメータは0個または1個の引数をとる手続きとして動作する
「パラメータ」は実際には0個または1個の引数をとる手続きと同様に動作します。引数なしで呼び出されたとき内部に保持した値を返します。引数つきで呼び出されたときは保持していた値を返し、引数の値を新たに保持します。
gosh> (use gauche.parameter) ;; gauche.parameterライブラリを使用する
#<undef>
gosh> (define x (make-parameter 20)) ;; 初期値20のパラメータを生成しxと名付ける
x
gosh> x
#<<parameter> 0x6b1c10> ;; xは<parameter>型
gosh> (x) ;; 引数なしで呼び出してxの値を得る
20
gosh> (x 100) ;; 保持されている値を得て100を新たにセットする
20
gosh> (x) ;; 引数なしで呼び出してxの値を得る
100
make-parameter手続きでパラメータを生成する
パラメータを生成するにはmake-parameter手続きを使います。make-parameterの引数はパラメータに与えられる初期値、戻り値は<parameter>型のインスタンスです。
大域変数と比較したパラメータの利点
大域変数に比べ、パラメータにはいくつかの利点があります。
- スレッドローカルである
- ダイナミックスコープに似た動作が実現できる
- セットされる値が正しいかどうかフィルタ手続きで検査できる
- オブザーバ手続きで値の変化を監視できる
パラメータはスレッドローカル
大域変数と異なり、パラメータの値はスレッドごとに保持されます。例えば複数のクライアントから同時に接続されるマルチスレッドサーバで同じ名前のパラメータを使っても、その値はスレッドごとに異なります。逆に、全部のスレッドで共有したい値を保持するにはパラメータではなく大域変数を使います。
parameterizeでダイナミックスコープを実現する
パラメータはダイナミックスコープと同様の動作を実現できます。
letで束縛された局所変数のスコープはレキシカルスコープです。
例:
gosh> (define x 0)
x
gosh> (define (foo)
(print x))
foo
gosh> (let ((x 3))
(foo)) ;; fooが参照しているxは3ではなく0
0
#<undef>
xを印字するfoo手続きから参照されるxは、letで束縛された局所変数の値にかかわらず常に大域変数xを参照します。
gauche.parameterライブラリには局所状態を作り出すparameterizeマクロが用意されています。letの例と異なり、parameterizeを使うとダイナミックスコープが実現できます。
例:
gosh> (define y (make-parameter 0))
y
gosh> (define (bar)
(print (y)))
bar
gosh> (parameterize ((y 3))
(bar))
3
#<undef>
gosh> (y)
0
パラメータyはparameterize構文の中でだけ3に書き換えられます。yの値を印字するbar手続きは変更された値3を印字します。parameterize構文を抜けるとyの値は初期値0のままになっています。
ダイナミックスコープはどんなとき便利か?
parameterizeによるダイナミックスコープは時間的な局所性を実現したいとき便利です。
例えばparameterize構文の中でデータベースを開き、データベースにアクセスしてデータベースを閉じるようにしたとします。こうすればデータベースを利用する一連の処理をparameterize構文の中だけに閉じ込めることができます。
parameterize構文の中で外部の手続きを呼び出すときも、外部手続きから見た「現在開いているデータベース」は一時的にそのとき開いているデータベースに変わっているのです。
さらに、parameterize構文の中でエラーが発生しても、データベースを安全に閉じてからparameterize構文を抜ける事もできます。
もちろんparameterize構文を抜けたときは大域的な値が復元されている事が保証されます。
フィルタ手続やオブザーバ手続きが使える
大域変数との大きな違いはフィルタ手続きやオブザーバ手続きです。フィルタ手続きは値を検査します。オブザーバ手続きは値が変化したとき外部にそれを通知します。本書ではこれらについて詳しく説明しません。Gaucheのリファレンスマニュアルを参照してください。