目次


Ruby on Rails究極指南: 第4回 話題騒然!「言語内DSL」の概念とRake(後編)

Comments

言語内DSLとしてのRuby

前回紹介したとおり、言語内DSLはホスト言語そのものを利用してDSLを構築します。したがって、言語内DSLではホスト言語の機能をフル活用できます。Rubyで構築された言語内DSLであれば、Rubyの制御構造の利用はもちろん、独自にクラスやメソッドを定義できますし、サードパーティーのRubyライブラリを利用することも可能です。

DSLからのRubyの利用

Rakefileは、実行時にそのままRubyのコードとして評価されます。ここで、前回紹介したhelloプログラム用のRakefile(リスト1)にRubyのメソッドを定義してみましょう。参考までにリスト1を再度示しておきます。

リスト1 Rakefile
 1 require 'rake/clean'
 2 CLEAN.include %w(hello *.o)
 3 
 4 task :default => ['hello']
 5 
 6 file 'main.o' => ['main.c', 'greet.h'] do
 7     sh 'cc -c -o main.o main.c'
 8 end
 9 
10 file 'greet.o' => ['greet.c'] do
11     sh 'cc -c -o greet.o greet.c'
12 end
13 
14 file 'hello' => ['main.o', 'greet.o'] do
15     sh 'cc -o hello main.o greet.o'
16 end

例えば、リスト1の6~12行目をリスト2のように修正すると、コンパイル部分をメソッドとしてくくり出せます。これを見れば、Rakeのファイルタスクのような成果物間の依存関係の宣言と、コンパイル方法のような具体的な手続きの記述とを、Rakefileに対して違和感なくまとめて簡潔に記述できていることが分かるはずです。ビルド時に必要な処理やその組み合わせが複雑になった場合、Rubyの機能をフル活用できることがRakeの大きなアドバンテージとなります。

リスト2 helloプログラム用Rakefileの修正
 6 def compile(dest, *src)
 7     sh "cc -c -o #{dest} #{src.join(' ')}"
 8 end
 9 
10 file 'main.o' =" ['main.c', 'greet.h'] do
11     compile 'main.o', 'main.c'
12 end
13 
14 file 'greet.o' => ['greet.c'] do
15     compile 'greet.o', 'greet.c'
16 end

Rubyの表現力

Rubyで実装された言語内DSLの文法の多くは、通常のメソッド呼び出しです。通常のメソッド呼び出しを、あたかもDSL用の新たな文法であるかのように読み書きできるというのがポイントです。このことは、DSLツールの実装者の負担も軽減します。

言語内DSLの文法としてRubyを利用しやすい理由には、次のようなRubyの特徴が挙げられます。

  • 省略表記の豊富さ
  • 言語レベルでのブロックのサポート
  • 拡張に対して開かれたクラス

以下、順番に紹介します。

省略表記の豊富さ

Rubyでは、多くの場合*、文末のセミコロンをはじめとして、メソッド呼び出しの括弧やハッシュのリテラルの記述などを省略できます。省略記述をうまく活用することで、Ruby上では単なるメソッド呼び出しにすぎない記述を、あたかも別の言語による宣言のように見せることができます。

Rakeのタスク記述を、

 task :test => [:compile] do
    do_task1
    do_task2
end

のように書けるのも、Rubyの省略表記のおかげです。この記述は、

task({:test => [:compile]})
{
    do_task1();
    do_task2();
}

と等価ですが、このように書いてしまうとDSLの文法というよりは、いかにもAPIメソッドの呼び出しのように読めてしまいます。

言語レベルでのブロックのサポート

Rubyでは、メソッド呼び出し時に「do……end」で括ったブロックを渡すことができます。ブロックには特定のコンテキストにおける一連の処理を記述します。

これを利用して、Rakeではタスクの定義にブロックを使います。

task :test doruby "test/unittest.rb"end

タスク定義にブロックを渡すことと、省略表記を活用することで、「task」がメソッド呼び出しというよりはタスク記述用の文法であるかのように見せています。

拡張に対して開かれたクラス

Rubyでは、あらゆるクラスが拡張に対して開かれています。コアライブラリのクラスであっても例外ではありません。これはRailsでの例になりますが、ActiveSupportというユーティリティはコアライブラリのNumericクラスを拡張しています。この拡張によって、Railsでは「現在時刻から20時間前」を「20.hours.ago」と記述できます。

 >> 20.hours.ago
=> Fri Jan 13 11:13:13 JST 2006

ActiveSupportのRubyコアライブラリ拡張は容赦がなく、true、false、nilといった表現をするクラスも拡張の対象としています。

言語内DSLとしてのRuby利用の広がり

ここまで読んだ人はすでに気づいているかもしれませんが、現在、最も成功しているRubyによる言語内DSLツールはRailsです。Railsは「Webアプリケーション」という問題を解決するためにRubyを利用しています。もちろん言語内DSLなので、Rails内ではRubyをフル活用できます。「Ruby on Rails」という名前は、「Railsという名のDSLのレールにRubyを乗せて行く」と解釈できるかもしれません。

このようなDSLライクな記述を活用したものはRailsの周辺ツール*でも充実しており、その代表的なものはmigrationとSwitchTowerです。前者はデータベースのスキーマのDDL*を、後者はRailsアプリケーションのデプロイ方法を、それぞれRubyを利用した言語内DSLとして記述します。

ここで、migrationでの簡単なテーブル定義を紹介しましょう。リスト3では、CreateUserTable#upというメソッドと、CreateUserTable#downというメソッドで、テーブルの作成と削除を定義しています(それぞれのSQL記述はリスト4)。これだけでは単にDDL記述を抽象化しているだけですが、migrationではデータベースのスキーマのバージョン管理も実現可能です。

リスト3 migrationでのテーブル定義
class CreateUserTable < ActiveRecord::Migration
    def self.up
        create_table :users do |t|
            t.column :name, :string, {:limit => 100, :null => false}
            t.column :hashed_password, :char, {:limit => 10, :null => false
        end
    end

    def self.down
        drop_table :users
    end
end
リスト4 リスト3をSQLで記述した例
4A  テーブルの作成

CREATE TABLE users (
    name VARCHAR(100) NOT NULL
    ,hashed_password CHAR(10) NOT NULL
);

4B  テーブルの削除

DROP TABLE users;

本稿で紹介してきたツールは、すべてRakeと組み合わせて利用できます。Railsはrailsコマンドで生成したアプリケーションの開発で利用するRakefileを、拡張可能な形式で提供しています。Rake本体の動作とは若干異なるRails流の規約ですが、それに従うことで簡単に拡張できるわけです。

ユーザー定義のRakeタスクをRailsの提供するRakefileと統合して利用するには、ユーザー定義のRakeタスクを記述したファイル(*.rake)をRailsアプリケーションの開発ディレクトリ以下にあるlib/tasksディレクトリに配置すれば完了です。これによって、ユーザー定義のRakeタスクとRailsの提供するRakeタスクとを同じように利用できます。

SwitchTowerは、この仕組みを利用しています。switchtowerコマンドでは、独自のRakeタスクが生成されます。このタスクはRailsアプリケーションの開発で利用するRakefileに統合されるので、違和感なく利用することが可能です。

まとめ

以上、2回にわたってDSLの概念とRakeについて解説してきました。「Rubyを言語内DSL構築用に利用して、簡潔な文法で宣言的な記述と手続き的な記述をうまく取り合わせ、それをRakeと組み合わせる」というケースは、これからもますます増えていくでしょう。

このページで出てきた専門用語

多くの場合

コンテキストによっては省略できない場合もあります。

Railsの周辺ツール

Railsと直接関係ないところでは、Rubiniumがあります。

DDL

Data Definition Languageの略。リレーショナルデータベースのテーブルを制御する言語。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, Open source
ArticleID=249933
ArticleTitle=Ruby on Rails究極指南: 第4回 話題騒然!「言語内DSL」の概念とRake(後編)
publish-date=05112007