PR

メタプログラミングと小言語

 ここまで紹介してきたようなメタプログラミング機能をあなたならどのように使いますか。

 Glenn Vanderburg氏*3によれば,メタプログラミング機能が最も活用できるのはDSL(Domain Specific Language)の分野なのだそうです。DSLとは特定の分野向けに機能を強化した小規模なプログラミング言語のことで,昔からあるアイディアです。ユーザーがアプリケーションを強化したり,カスタマイズしたりするために活用されるなど,最近再び注目されている考え方です。同氏によればDSLは問題分野に特化するため,表2のような機能を備えていることが望ましいのだそうです。

表2●小言語が備えるべき機能
表2●小言語が備えるべき機能

 このうち,「型」から「制御構造」まではRubyに元々備わっています。多くのDSLはミニ言語でこの辺り手を抜いていることが多いので,かえって使いやすいかもしれません。また,前回学んだようにRubyはブロックを使って制御構造を実現するメソッドを自分で定義できるというのも利点です。

 残りの「宣言」から「階層データ」まではRuby自身に備わった機能で実現できます。この実現にはRubyのメタプログラミング機能が活躍するのです。

Rubyの宣言

 表2にある「宣言」について考えてみましょう。

 今回,冒頭でattr_accessorをメタプログラミングの例として紹介しました。Rubyとして見たときには単なるメソッド呼び出しですが,宣言として考えることもできます。ActiveRecordの例でもhas_manyなど宣言として考えることができるメソッドの例はたくさんあります。

 Rubyではメソッドがプログラム自身の状態を読み出したり,変更したりできるので,通常のメソッド呼び出しで,他の言語であれば「宣言」によって進めるような内容を実現できます。

 外見の点からは,Rubyのメソッド呼び出しはかっこの省略ができる点,また「名前を表現するもの」として:fooのような「シンボル」が使える点でより宣言らしいプログラムの見かけが実現できています。

Rubyの文脈依存

 次は「文脈依存」です。文脈依存とは文脈によってある一定の範囲だけ語彙(ごい)をすりかえることです。少々人工的な例ですが,図8のようなプログラムを考えます。

add_user {

 name "Charles"
 password "hello123"
 privilege normal

}
図8●文脈依存のプログラムの例

 この例ではadd_userで指定したブロックの範囲内でだけ,nameやpasswordなどのメソッドが有効になっています。つまりadd_userのブロックの外側ではこれらのメソッドは見えないわけです。

 Rubyレベルではこれはブロックの範囲内だけメソッドの受け取り手であるselfをすりかえることで実現しています。具体的には図9のようにinstance_evalメソッドを使います。

def add_user(&block)

 u = User.new
 # User class has name, password,
 # privilege methods
 u.instance_eval(&bock) if block

end
図9●図8でコンテキストをすりかえている処理

 instance_evalメソッドがブロックを受け取ると,selfを置き換えた状態でブロックを実行します。結果として,図8の例ではブロックの範囲内でデフォルトのレシーバがUserクラスのインスタンスuになります。そのため,レシーバを指定せずに実行するメソッド(nameなど)としては,Userクラスのメソッドが呼び出されます。

Rubyの単位

 一般的なプログラミング言語では値として「スカラー値」を扱います。これは数そのものです。その数が表現している単位はプログラマ側で管理する必要があります。

 一方,DSLで扱いたいのは単なる数ではなく「量」であることが多いのです。そのため,いくつかのDSL的アプローチに則ったライブラリでは「単位」を取り扱うように拡張されています。

 例えばRuby on RailsではNumericクラスとTimeクラスに時間を扱うための単位メソッドが追加されています(基本単位は秒)。例えばある時間を表現するためには

3.years + 13.days + 2.hours

と書きます。するとこれは「3年と13日と2時間」を秒で表現した整数95803200となります。また,次のようにして「今から4カ月後の月曜日」を表す時刻を得ることができます。

4.months.from_now.monday

 原稿執筆時に計算した結果は以下のようなものでした。

Mon Dec 12 00:00:00 JST 2005

 これは時刻と時間に関する例ですが,既存のクラスにメソッドを自由に追加できるRubyでは,このように単位を表現するメソッドを簡単に実現できます。

Rubyの語彙

 DSLが目的分野に特化しているというのは,結局,どれだけその目的分野で行われる処理を表現する語彙を持っているかということでしょう。ある分野で必要としているクラスやメソッド,手続きをRubyで定義するとは,Rubyをその分野向けの専用言語化することだと考えられます。そのようなクラスやメソッドのことをその分野における「語彙」と呼んでも良いでしょう。

 「達人プログラマ」として知られるDave Thomas氏の言葉を借りれば「すべてアプリケーションを作る過程は結局言語をデザインすることである」のだそうです。その見方に従えばアプリケーションを作ることは,そのアプリケーションの問題領域の語彙をどんどん定義していき,最後にその語彙を使って問題解決手段を記述することにほかなりません。

 Rubyのメソッド呼び出しやブロックなどの表現力を使うと,ユーザーにとってより自然な形で語彙を定義できます。また,語彙があらかじめ決定できない場合には,DelegatorやXmlMarkupのようにmethod_missingという手法を使って動的に語彙を追加・利用できます。

Rubyの階層データ

 最後に表2の末尾にある「階層データ」を説明します。先ほど紹介したXmlMarkupはまさに階層データの表現になっています。図6を再び眺めてみましょう。プログラムとして見たときには単なるブロック付きのメソッド呼び出しがネストしているだけのことですが,外見上も機能上も立派な階層データの表現です。