Rubyのアノテーション

2006年10月26日

Rubyの最も人気のある機能の一つは、メタプログラミングのサポートです。メタプログラミングとは、まるで言語自体を変更するかのごとく振る舞い、新しいキーワードなどを導入する機能です。

主流の中括弧言語は、一般的にメタプログラミングのサポートは少ないですが、アノテーションは有用な機能です。アノテーションは、言語拡張の内部DSLスタイルにとって重要な機能です。一見するとRubyはアノテーションをサポートしていないように見えますが、見方を変えるとサポートしていることがわかります。

アノテーションを使用すると、言語要素(クラス、メソッド、フィールドなど)にマーカーを付けることができます。これは基本的にその要素に関するメタデータです。このメタデータは、実行時またはコンパイル時に使用できます。

この良い例は、NUnit2によって先駆的に導入されたテストのマーキングです。

[Test] public void SomeTestMethod() {...

実行時に、NUnitフレームワークはクラスを調べ、[Test]でアノテーションが付けられたメソッドを見つけ、それらを実行します。

アノテーションはパラメータを取ることができます。そのため、変数の有効な範囲を示すことができるPascalのサブレンジが好きな場合は、次のように定義します。

  
@ValidRange(lower = 1, upper = 1000)
  private int weight; // in lb
  @ValidRange(lower = 1, upper = 120)
  private int height; // in inches

これはPascalのサブレンジの考え方とは少し異なります。これは有効な範囲を定義するだけで、オブジェクトのisValidメソッドを呼び出すなど、値をチェックするメカニズムを構築する必要があります(これはコンテキスト検証としてはあまり良くないことに同意します)。しかし、ここで重要なのは、その変数について独自の宣言型ステートメントを定義したことです。

では、Rubyではどのようにこれを行うのでしょうか?次のような感じです。

	validate_range :@height, :with => 1..120
	validate_range :@weight, :with => 1..1000

構文の面で大きな違いは、Java(または.NET)のアノテーションでは、アノテーションをアノテーションする要素の直前に配置することです。Rubyでは、アノテーション内でアノテーションする要素の名前を指定します。これはタイピングを増やすことになりますが(Rubyでは珍しいことです)、アノテーションをクラス内のどこにでも配置できるという自由度が得られます。また、複数の言語要素を参照するアノテーションを構築しやすくなります。

2つのスタイルのより深い違いは、それらがどのように実装されるかです。中括弧アノテーションは、特別なオブジェクトを言語要素にメタデータとして添付する特別な言語構成要素です。これらのアノテーションオブジェクトは、コンパイル時(Javaのaptを使用)または実行時にクエリおよび処理できます。

一方、Rubyのアノテーションは、通常はスーパークラスまたはインクルードされたモジュールで定義されるクラスメソッドです。クラス定義に直接記述することで、クラスのロード時に実行されます。そのため、特定の言語機能ではなく、クラスメソッドの使い方の一種です。メタデータオブジェクトを作成する必要はありません(必要に応じて作成することもできます)。タスクを実行するためのオブジェクトを構築するだけで済みます。

Rubyの動的な性質により、アノテーションでより多くの興味深いことができます。特に、コード生成を行うことができます。この最も明白な例は、attr_accessor :heightのようなもので、フィールドのgetメソッドとsetメソッドを生成し、効果的にクラス自体を変更します。aptを使用すると、Javaでも同様のことができますが、クラス自体を変更することはできません。ビルドスクリプトと部分クラスを使用すれば、C#でも同様のことができるでしょうが、この種の実行時コード生成はRubyでは確かにより自然であり、Lisp的な要素の1つです。

Rubyistはこれらのことをアノテーションとは呼びません。私が好きなことの1つは、言語を横断する共通のテクニックを見つけることです。私にとってこれは共通のテクニックであり、「アノテーション」はそれに対する適切な一般的な言葉のようです。Rubyistが同意するかどうかはわかりません。