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

近年、特定の領域に存在する問題の解決に特化してデザインされたコンピュータ言語「DSL」が注目を集めている。本稿では、前回に引き続いて、RubyとDSLの関係について解説していく。

» 2007年03月26日 10時35分 公開
[角谷信太郎,ITmedia]

言語内DSLとしてのRuby

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

DSLからのRubyの利用

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


 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 Rakefile

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


 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

リスト2 helloプログラム用Rakefileの修正

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 do

    ruby "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ではデータベースのスキーマのバージョン管理も実現可能です。


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
リスト3 migrationでのテーブル定義

4A テーブルの作成

CREATE TABLE users (     name VARCHAR(100) NOT NULL     ,hashed_password CHAR(10) NOT NULL );
4B テーブルの削除
DROP TABLE users;
リスト4 リスト3をSQLで記述した例

 本稿で紹介してきたツールは、すべて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がある。

migration

http://wiki.rubyonrails.com/rails/pages/UnderstandingMigrations

DDL

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


本記事は、オープンソースマガジン2006年3月号「Ruby on Rails 1.0の世界」を再構成したものです。


Copyright © ITmedia, Inc. All Rights Reserved.

注目のテーマ