目次


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

Comments

RubyとDSLの関係

近ごろ、DSLという考え方が注目を集めています。DSLとは、特定の領域(ドメイン)に存在する問題の解決に特化してデザインされたコンピュータ言語のことを指します。

DSLの考え方は新しいものではありません。例えば、awkは「テキストをフィルタリングする」という問題を解くことに特化したDSLと考えられます。そのほか、自分で特定の問題を解くためにミニ言語を設計・実装された経験のある方もいるでしょう。

ここでは言語指向のDSLとRubyとの親和性の高さを、Rakeを題材にして紹介します。

ビルドツールRake

RakeはRubyで実装されたビルドツール*であり、ジム・ウェイリッチ(Jim Weirich)氏が中心となって開発(2007年3月時点での最新版は0.7.2)しています。Rakeの利用例の筆頭は、もちろんRuby on Rails(以下Rails)です。「Railsの利用を通じてRakeの存在を知った」という人も多いのではないでしょうか。

まずは、ビルドツールの役割などから説明していきます。

ビルドツールとDSL

ビルドツールの利用方法として最も多いのは、ソフトウェア開発におけるソースコードのコンパイルです。しかし、ビルドツールを適用できる場面はこれだけに限りません。このツールが行っていることをもう少し一般化して考えると、ビルドツールが取り組みんでいる問題領域は「時系列的に依存関係のある成果物に対する処理の自動化」といえます。例えば、図1のように「成果物Dが成果物Bと成果物Cに、成果物Eが成果物Cに、そして成果物Bと成果物Cは成果物Aに依存している」といった状態です。ここでいう「成果物」は主にファイルのことを指しますが、コンピュータが処理結果として出力するものであればファイル以外でも構いません。特定のWebサイトのページをHTTPクライアント経由で取得した文字列ストリームなども成果物と考えられます。

図1. 成果物の依存関係
図1. 成果物の依存関係
図1. 成果物の依存関係

ビルドツールを利用するには、ビルドファイルを用意して、ビルドツールに対する指示を記述します。RakeのビルドファイルはRakefileであり、これはmakeのMakefile、antのbuild.xmlに該当します。

ビルドファイルには、ビルドツールが自動化するタスクの依存関係の宣言と、タスク本体の処理手続きを記述します。この書式は、ビルドツールごとに独自のもの(文法)となっています。従って、ビルドファイルは「処理の自動化という問題領域を記述するための言語」、すなわちビルド用DSLととらえることもできるわけです。マーティン・ファウラー(Martin Fowler)氏*は、このようなビルド用DSLのことを「ビルド言語」と呼んでいます。

コラム:動く疑似コードRuby

当初、ジム・ウェイリッチ氏はRakeのようなビルドツールを作るつもりはなかったようですが、「MakefileをRubyで書けたら素敵なのになあ」という同僚との雑談がきっかけとなり、開発を始めたようです。

最初のRakefileのスケッチは次のようなものでした。

target "compile" do
  java.compie JAVA.SRC
end

このように記述できるビルドツールが作成可能かどうかを確認するため、プロトタイプを作ったところ、100行程度のRubyコードによって15分間で実装できたそうです。疑似コードのような簡潔な記述がそのまま簡単に動かせるように実装できるのは、Rubyの手軽さと生産性を物語っているように思います。

ここで紹介したプロトタイプのソースコードは、いまでもRakeのドキュメントにバンドルされています。興味のある方は参照してください。

ビルド言語の分類とRake

DSLはスタイルによって幾つかに分類できますが、ここではプログラミング言語指向のものに注目します。プログラミング言語指向のDSLは、言語内(internal)DSLと異語外(external)DSLに大別できます。言語内DSLとは実行時に利用される言語(ホスト言語)とDSLが同じものを、言語外DSLはホスト言語とDSLが異なるものを指します。いずれも以前から行われているアプローチであり、例えば言語内DSLはLispやSmalltalkで、言語外DSLは多くのUNIXツール(例えばawkやprocmail、PostScript)で使用されています。

マーティン・ファウラー氏は、make、Ant、Rakeの各ビルド言語をDSLのスタイルに従って次のように整理しています。

  • make:独自文法による言語外DSL
  • Ant:XMLを言語として利用した言語外DSL
  • Rake:Rubyによる言語内DSL

DSLとしてホスト言語を利用するメリットは、学習コストの節約と、ホスト言語の文法や機能、周辺ツールを活用できる点にあります。「ドメインに特化した言語」という観点からはデメリットともなりますが、ビルドツールのようなユーザー層がプログラマーである分野では、デメリットよりもメリットの方が大きいでしょう。

Rakeの簡単な使い方

それでは、実際にRakeを使ってみましょう。Railsが導入済みであれば、Rakeもすでにインストールされています。Rakeが正常にインストールされているかどうかは、rakeコマンドを実行することで確認できます。

$ rake --version
rake, version 0.7.2

なお、ヘルプを表示したい場合は、「--help」オプションを指定します。

ビルドするプログラムの概要

ここでは、Cで書かれた「Hello World」を出力するプログラムをビルドします。ソースコードの内容は省略しますが、全体の依存関係が図2のようになっているケースを想定します。

図2. helloの依存関係
図2. helloの依存関係
図2. helloの依存関係

コンパイル対象のソースコードとヘッダファイル、RakeのビルドファイルであるRakefileは、すべて同じディレクトリに配置しておきます。

$ lsgreet.c greet.h main.c rakefile

Rakefileの記述例

helloプログラムをビルドするには、リスト1のようなRakefileが必要となります。Rubyの基本的な文法を理解していれば、Rakeの詳細を知らなくても処理内容が読み取れるのではないでしょうか。Rakefileの内容は次のとおりです。

リスト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行目では、Rakeがビルトインで提供しているcleanタスクを読み込んでいます。2行目では、cleanタスクで削除対象とするファイルを配列で指定しています。ここでは最終成果物であるhelloと、*.cをコンパイルして生成するオブジェクトファイルが削除対象です。

4行目は、rakeコマンドを引数なしで起動した場合に実行されるデフォルトのタスクです。Rakeの基本的な文法は、

task タスク名 => [依存タスク] do
    処理内容
end

というものです。タスク名の指定にはシンボルを使用することに注意してください。

6行目以降では、ビルドに必要な各処理をそれぞれタスクとして定義しています。「task」ではなく「file」と記述しているのは、ファイル生成に特化したタスクだからです。Rakeではこれを「ファイルタスク」と呼んでいます。ファイルタスクが通常のRakeタスクと異なる点は次の2つです。

  • タスク定義では「task」の代わりに「file」と記述
  • ファイルタスク名はシンボルではなく文字列で指定

ブロックには、ファイルタスク名で指定したファイルを生成するための処理を記述します。「sh」はRakeが提供しているメソッドであり、与えられた文字列をシステムコマンドとして評価・実行します。

なお、実際にRakeを利用する際には、タスク定義の重複個所は「ルール」と呼ばれるRakeの機能や、Rakeが提供する変数などを使ってパターン化できます。この例では説明を簡単にするため、重複個所はそのままにしてあります。

ファイルタスクと同様の位置づけのタスクとして、ほかに「ディレクトリタスク」が提供されています。名前のとおり、ディレクトリの生成に特化したタスクです。書式は、

directory "ディレクトリ名"

であり、ディレクトリ名はファイルタスクと同様に文字列で指定します。

  • rakeコマンドによるビルドの実行

Rakefileで定義したタスクを実行するには、Rakefileを置いたディレクトリでrakeコマンドを実行します。リスト1のRakefileではdefaultタスクを定義しているので、引数なしで起動するとhelloのファイルタスクが実行されます。

$ rake
(in /path/to/rakefile/directory)
cc -c -o main.o main.c
cc -c -o greet.o greet.c cc -o hello main.o greet.o

生成されたファイル群を確認し、helloが実行できることを確認します。

$ ls
greet.c greet.h greet.o hello main.c main.o rakefile
$ ./hello
Hello, World

再度rakeコマンドを実行しても、ファイルに変更がな

  $ rm greet.o
$ rake
(in /path/to/rakefile/directory)
cc -c -o greet.o greet.c
cc -o hello main.o greet.o

のようにgreet.oが再生成され、greet.oに依存しているhelloが再コンパイルされます。これによって、Rakefileに記述した成果物の時系列的な依存関係がRakeによって解決されていることが分かります。

cleanタスクを実行すると、削除対象としたファイルが削除されます。

$ rake clean
(in /path/to/rakefile/directory)
rm -r hello
rm -r main.o
rm -r greet.o
$ ls
greet.c greet.h main.c rakefile

次回は

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

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

ビルドツール

既存のビルドツールとしては、makeやAntが有名です。

マーティン・ファウラー氏

オブジェクト指向分析、オブジェクト指向設計、UML、分析パターンをはじめとしたソフトウェアパターン、アジャイルソフトウェア開発方法論の分野において、活発に活動しているソフトウェア技術者です。


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


関連トピック


コメント

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

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