レベル: 初級 まつもとゆきひろ, Writer, ITmedia
2007年 5月 25日 「ガーベジコレクション」「リフレクション」「アスペクト指向」「クロージャ」「イテレータ」「型推論」など、近年、プログラミング言語の世界に新しい概念が続々となだれ込んでいるように見えます。しかし、この背景には、実はあまり知られていない歴史が隠されているのです。
新潮流の実体
前回紹介したように、「知られざる言語」で熟成された機能が、メインストリームの言語に続々と取り込まれているのが「新潮流」の真実だったわけです。メインストリームの言語しか見ていなかった人にはまったく新しいものに見えますが、実際にはもう何年も何十年も使われてきた技術だったのですね。
ここにきてなぜ、新技術がメインストリームに取り込まれるようになったのか、はっきりとした理由は分かりません。しかし、おそらくは以下のような原因があるのではないでしょうか。
1.コンピュータの性能向上
ハードウェアの進歩による性能向上で、以前は受け入れられなかったような性能上のペナルティがある技術も受け入れられるようになったこと。例えば、オブジェクト指向プログラミングやガーベジコレクションなど。
2.ソフトウェアの複雑化
開発すべきソフトウェアはどんどん複雑化しており、旧来の言語による開発に限界が見えてきたこと。より生産性を向上させるような技術に、貪欲にならざるを得なくなった。
3.技術者の意識向上
同じ技術に固執したがるのは人間の性だが、それでも開発効率のために新しい技術や概念を探求しようという意識が、技術者の間で高まったこと。
そして、そのような開発効率向上技術が、いままで主に学術分野で使われていた「あまり知られていなかった言語」から取り込まれるようになったのではないでしょうか。
ガーベジコレクション
ガーベジコレクションは、プログラムの実行中に使われなくなったオブジェクトを自動的に検出して、そのメモリ領域を解放する技術です。Javaによって一躍普及し、一般に受け入れられるようになった技術ですが、Lispではその当初から採用されていました。ガーベジコレクションに関する初期の論文は、1960年代半ばにはもう発表されています。
ガーベジコレクションは誕生から40年以上たつ現在でもいまだにホットな話題ですが、ここではLisp周辺で古くから用いられていたことを紹介するだけにとどめることにします。
高階関数とクロージャ
高階関数とは「関数を引数に取る関数」のことです。関数や手続きを引数として渡すことで、実装の詳細を見ずに、「やりたいこと」に集中できます。例えば、Lispの方言であるSchemeにはmapという高階関数があり、リストの各要素に対して関数を実行できます(リスト1)。この例ではmapに関数を渡すことで、リストの各要素へのアクセス方法など実装の詳細については隠したまま、要素1つずつに対して行うべき処理だけに集中できます。実装の詳細を隠蔽するのは、「抽象化」といってプログラミングの基本的なテクニックです。
リスト1 高階関数mapのサンプルプログラム(Scheme)
(define (print-elem n) (* n 2))
↑ 引数に受け取った値を2倍する関数print-elemを定義
(map print-elem '(1 2 3))
↑ 高階関数mapを使って、3つの数値(1、2、3)からなるリストそれぞれに、print-elem関数を適用
# => (2 4 6)
↑ リストの値がそれぞれ2倍された
|
高階関数を実現するためには、プログラム言語は関数あるいは手続きをデータとして取り扱うことができる必要があります。
Rubyのブロックは、文法上の見かけこそSchemeと少々異なっていますが、やっているのは本質的に高階関数と同じことです。リスト1のプログラムをRubyのブロックを用いて表現したものがリスト2です。Rubyのブロックは名前のないのがデフォルトなので、Schemeと比べてプログラムがややシンプルになっています。
リスト2 高階関数mapのサンプル(Ruby)
[1,2,3].map{|n| n * 2}
# => [2, 4, 6]
|
もう1つ別の例としてソートを考えてみましょう。配列要素のソートを行う場合、比較する条件を変更したいことはよくあります。そのようなときにも高階関数を用いて解決できます。リスト3はRubyで配列をソートするsort_byメソッドの利用例です。リスト3のソートは、「奇数を降順に、次に偶数を昇順に並べる」という一風変わった基準でソートしていますが、複雑な処理の割に非常に簡潔に記述できています。
リスト3 高階関数sort_byのサンプル(Ruby)
p [4,2,3,6,1].sort_by{|x| if x % 2 == 0 then x else -x end}
↑奇数を降順に、次に偶数を昇順に並べるソート
# => [3, 1, 2, 4, 6]
|
ソートについては、Cでも関数を引数に取るqsort関数が提供されています(リスト4)。これも高階関数ですね。
リスト4 C言語のqsort(3)のAPI
#include <stdlib.h>
ソート対象となる配列へのポインタ
│
│ 要素の個数 要素1つ当たりの大きさ
↓ ↓ ↓
void qsort(void *base, size_t nmemb, size_t size,
int(*compar)(const void *, const void *));
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
↑ 2つの値を受け取って、それらを比較する関数へのポインタ
|
高階関数があるとクロージャも欲しくなります。クロージャとは、スコープの外側の変数への参照を「閉じ込めた」関数オブジェクトです。リスト5にクロージャと高階関数を組み合わせたサンプルを示します。
リスト5 クロージャと高階関数のサンプルプログラム(Scheme)
(define (xtimes x) (lambda (n) (* n x)))
↑ 「引数に受け取った値をx倍する関数」を返す関数xtimesを定義
(map (xtimes 5) '(1 2 3))
↑ (xtimes 5)で、「引数に受け取った値を5倍する関数」を生成。
高階関数mapを使って、それを3つの数値(1、2、3)からなるリストそれぞれに適用
# => (5 10 15)
↑ リストの値がそれぞれ5倍された
|
「xtimes」関数は、整数を引数として受けて、それを整数倍する関数を返す関数です。ちょっと分かりにくいかもしれませんが、
の値をfとすると、
は「20」を返します。関数オブジェクトは「lambda*」式によって作成し、「(n)」の部分がlambdaの返す関数の引数で、「(* n x)」の部分が関数定義になります。ここでnは引数ですが、xはlambdaの外側の変数(xtimes関数の引数)です。lambdaが作り出す関数オブジェクトは、変数xを自分の中に取り込んでいるわけです。
クロージャがあると、高階関数の使い勝手が非常に向上します。例えばクロージャがなければ、せっかく高階関数を使ってループを抽象化しても、ループの外側の変数を参照できないことになってしまいます。それでは高階関数による抽象化のメリットが台無しです。
最近の動的言語の多くは何らかのクロージャ機能を提供していますし、Javaでさえ、2008年リリース予定のJDK7でクロージャを提供するという話が聞こえてきています。
リフレクション
「リフレクション」とは、プログラムがプログラム自身を操作する機能です。例えば、現在実行中のプログラムに関する情報を得たり、現在実行中のプログラムを書き換えたり(メソッドやクラスを追加/変更など)する機能が、リフレクションに相当します。プログラムそのものを扱うプログラミングであることから、しばしばメタプログラミングと呼ばれます。自分自身でインタープリタが記述されることの多いLispなどの言語では、リフレクションは言語の本質的機能の一部です。
リフレクションは非常に強力で、いろいろなことが可能になります。例えば、Rubyで記述されたWebアプリケーションフレームワークであるRuby on Railsでは、Rubyのリフレクション機能を使って、
- データベーススキーマに対応したオブジェクトを作る
- オブジェクト間の関係を維持する機能を生成する
- バリデーションを行う機能を生成する
などの処理を行っています。これによって、人間がいちいちプログラムを書き換えたり設定ファイルを用意したりする手間が減り、生産性を向上させています。
アスペクト指向
「アスペクト指向」は複数のクラスを横断するような機能(例えばデバッグ用にさまざまなメソッド呼び出しのログを取る機能)を、ひとまとめにして管理する機能です。従来のオブジェクト指向言語では、機能をクラスに従って組織化します。しかし、「ログを取ること」のように複数のクラスにまたがる機能は、あちこちのクラスに分散してしまいがちです。このような機能は1つの関心事に所属するので、できればひとまとまりの塊として扱いたいものです。これを「関心の分離(Separation of Concerns)」と呼び、機能をクラスから「引きはががして」まとめる単位となるのがアスペクトです。
アスペクト指向は比較的新しい概念ですが、その基礎になっているのは、Common Lispのオブジェクト指向機能であるCLOS*に含まれている「メソッドコンビネーション」という技術です。実際、アスペクト指向の提唱者Gregor Kiczalesは、CLOSの設計者の1人でもあります。
型推論
JavaやC++のように、変数や式に型のある言語*では、プログラムを解析することでプログラム自身に型の矛盾があるかどうかを検出できます。プログラム中の多くのエラーは型の矛盾によって検出できますから、コンパイルするだけで実際に実行しなくても網羅的にエラー(の一部)を検出できるのは大変うれしいことです。
しかし、変数や関数などにいちいち型宣言を行うのはいろいろと面倒を伴います。そのこともあって、Javaのプログラムと型宣言のないRubyのプログラムを比較すると、Javaの方がずいぶん冗長に見えます。また、いちいち型を考えないといけないのは、プログラマーにとって少々負担です。
「型推論」は、静的型のメリットはそのままに、型指定の手間を省く技術です。MLやHaskellなどの関数型言語では広く採用されている技術で、定数や定義済み関数の型などを手がかりに、変数や式の型をできる限りコンパイラが推定してくれます。ですから、プログラマーは型を指定する必要がほとんどありませんし、それでいて型の矛盾はコンパイル時に検出されます。
このページで出てきた専門用語
lambda
「ラムダ」と読みます。Lispの数学的背景となる「ラムダ算法」からきています。関数を定義する構文のことです。
CLOS
Common Lisp Object Systemの略。当初のCommon Lispには含まれておらず、後から追加になったため独立した名前がついています。多重継承、マルチメソッド、メソッドコンビネーションなど、よく見かけるオブジェクト指向機能とは一線を画しているのが特徴です。発音は、「クロス」でも「シーロス」でも何でも構わない、というのが公式見解らしいです。
変数や式に型のある言語
このような言語は「静的型」に分類されます。一方、Rubyのように変数や式に型を指定しない言語は「動的型」に分類されます。
まとめ
2回にわたって、近年メインストリームのプログラミング言語に取り込まれつつある「新技術」の幾つかについて概観してみました。現代のプログラミング言語は、生産性向上のための新しい技術を貪欲に取り込みつつあります。
プログラミング言語は、IDEなどのツールと並んでプログラミング効率を左右する存在です。プログラミング言語の進化には、これからも注目していきたいと思います。
参考文献
著者について  | |  | ITmedia |
記事の評価
|