関数を引数に取る高階関数は,C言語で標準的に利用されるなど,必要不可欠な機能です。ただし高階関数を用いるとコードが複雑になるという欠点があります。Rubyのブロックやクロージャは,高階関数を利用しやすくした構造です。
今回はRubyの特徴的な機能である「ブロック」という構文について解説しましょう。Rubyでいうブロックとは「メソッド呼び出し時に追加できるコードの塊」を意味します。
例えば,配列の各要素に対する繰り返し処理を進める次のコードを見てください。
ary.each {|x| puts x}
この例ではかっこ(ブレース)に囲まれた領域{|x| puts x}がブロックに相当します。これだけで,「各要素を変数xに代入した上で,putsを実行する」という意味になります。
Rubyにあるブロックは,Rubyオリジナルの機能ではありません。他の言語に古くから存在していたものを文法的にアレンジしたものです。ブロック機能の発想の基になった他言語の機能も含めて解説しましょう。
関数を引数に取る高階関数
Rubyのブロック機能は原理的には「高階関数」と呼ばれるものと同じです。高階関数とは「関数を引数に取る関数」を指します。
高階関数を実現するためには,関数あるいは手続きをデータとして取り扱えるようなプログラミング言語が必要です。古い言語,例えばFORTRANなどでは高階関数を実装するのは不可能でしたが,C言語では可能です。C言語は関数ポインタという形で関数をオブジェクト*1として扱えるからです。
C言語ではライブラリ関数でも高階関数が使われています。広く使われている例としてはqsort(3)*2があるでしょう。qsortは図1[拡大表示]に示すようなAPIを持ちます。高階関数がでてくるのは第4引数,ここでは,comparという関数ポインタを受け取っています。
qsortはポインタbaseで指定されたメモリー領域を,各要素の大きさがsizeであり,要素数がnmemb個である配列として解釈し,要素をクイック・ソート・アルゴリズムでソートします。ソートするためには各要素の大きさを比較する必要がありますが,その比較のためにcomparで指定した関数を呼び出します。comparは引数として2つのポインタを受け取り,それぞれのポインタが指すオブジェクトの大小比較を行って,第1引数の方が大きければ正の値,等しければゼロ,小さければ負の値を返す必要があります。
qsort関数の実際の使用方法を見てみましょう。図2[拡大表示]はqsortの使用例です。図2のプログラムはコマンドライン引数として与えられた文字列をソートします*3。実行結果を図3[拡大表示]に示します。
qsortでは文字列の配列をソートしようとすると,比較関数には文字列(char*)へのポインタが渡されます。ポインタのポインタが登場するのが少し難しいところです。
Cの高階関数の限界
引き続き,C言語を題材に別の例を見てみましょう。Rubyのソース・コードにはハッシュ・テーブルを実装するライブラリが含まれています*4。
このライブラリにはハッシュの各要素に対して順に操作するループ用関数st_foreach()が提供されています(図4[拡大表示])。st_foreach()はハッシュの各要素に対して,キー,値,foreachの第3引数の3つを渡します。第3引数argは,st_foreach()に渡されたものをそのまま処理関数funcに渡すためのものです。ハッシュ要素を示すキーと値はともかく,どうしてargが必要なのでしょうか。
これはC言語には関数を越えて情報を共有する手段が2つしか用意されていないからです。引数として明示的に渡すか,グローバル変数を使うかのいずれかしかありません。
しかし,グローバル変数を使って情報を受け渡すと,いつ,だれがその変数を参照・更新するか分かりませんし,再帰的呼び出しができなくなってしまいます。これは関数を越えて情報を共有できないCのような言語の制限です。
外部環境を保存できるクロージャ
このようなC言語の制限は,Javaなどのほかの言語にも当てはまります。一方,Rubyでは外側のスコープの変数を参照できるように新たに設計されています。
図5[拡大表示]のRubyプログラム*5はリストから指定した長さの要素を抽出するためのものです。selectはブロックを実行した結果が真のものを集めた配列を返すメソッドです。このため,high_paidメソッドを使うと引数として渡したコレクションからsalaryが150以上のものを配列にまとめて返すことができます。
先ほどの例のようにC言語の関数ポインタを用いる場合と比較すると,
●使うその場で定義できる
●外側のローカル変数が参照できる
という2つの点からRubyの方が使いやすいことが分かるでしょう。もちろん,Javaの無名クラスを使ったり,C言語の関数ポインタを使っても似たような処理を実装できます。しかし,図5のRubyプログラムほど簡潔に実現できるとは思えません。
このように外側のローカル変数を参照できるのは,ブロックが単なる命令列としてのコードだけでなく,外側の「環境」も「閉じ込めている」からです。このようなブロックをクロージャと呼びます。通常ローカル変数などはメソッドの実行が終了すれば消えてなくなりますが,クロージャに閉じ込められた場合,そのクロージャが生きている限り,参照できる形で残ります。