第6回 eRuby

前田修吾

プライベートインスタンス変数

例によって今回のテーマとは関係のない話からはじめます。

Bertrand Meyerの著書に、「オブジェクト指向入門」[1]という、オブ ジェクト指向のバイブルとも言うべき本があります。その本の中で、開放/閉 鎖原則(Open-Closed Principle)という、オブジェクト指向において重要な概 念が出てきます。

開放/閉鎖原則とは、

モジュールは開いていると同時に開じていなければならない。

というものです。(ここでいうモジュールとはRubyではクラスやモジュールの ことです。ややこしいですね。) 開いていると同時に開じている、というのは 何だか哲学的な表現ですね。*1こういう表現は深い意味がありそうに聞こえるのである 種の人々には好んで使われますが、あまり意味がないことも多いので注意が必 要です。もちろん、開放/閉鎖原則にはきちんとした意味があります。モジュー ルが開いている、あるいは閉じているというのは、以下のような状態を指しま す。

モジュールの開放性

モジュールが開いているというのは、そのモジュールを拡張することがで きるということです。新しい属性や機能をモジュールに追加することがで きる時、そのモジュールは開いていると言います。

モジュールの閉鎖性

モジュールが閉じているというのは、そのモジュールを他のモジュール (クライアントモジュール)から利用できるということです。一見、これは 開じているという言葉と反対のことを意味するように思われますが、モ ジュールを他のモジュールから利用できるためには、そのモジュールの公 開インタフェイスが安定していて、実装についてはモジュール内部に開じ ている必要があります。

これらの要求は矛盾するように見えますが、Ruby(を含むオブジェクト指向言 語)では継承によって解決されます。Rubyではインスタンス変数は外から直接 アクセスすることはできません。*2 また、内部的にのみ使用するメソッドをprivateにすることで、利用者に公開 したいインタフェイスだけを提供することができます。このようにRubyのクラ スは開じています。また、Rubyのクラスは継承によって拡張を行うことができ ます。Rubyのprivateは、JavaやC++でいうprivateよりもprotectedに近いもの で、サブクラスからはすべてのインスタンス変数・メソッドにアクセスし、自 由に拡張を行うことができます。つまりRubyのクラスは開いています。

JavaやC++のユーザはサブクラスに対する制限ができないことを不満に思うか もしれません。しかし、スーパークラスの設計段階では、後にサブクラス化さ れてどのような拡張が行われるかということについて、すべての可能性を検討 することは不可能です。これはサブクラスに公開すべきものと公開すべきでな いものの判断が非常に難しいということを意味します。したがって、モジュー ルの開放性という観点からみると、サブクラスに対する制限を行わない(行え ない)ということには意味があります。(インスタンス変数に関しては自クラス からもアクセスメソッドを介してアクセスすべきだという考えもありますが。)

ただ、この開放性には問題もあります。スーパークラスの実装を利用するとい うことは、スーパークラスの実装が変更された場合(たとえばインスタンス変 数名が変更された場合など)には、その都度追随しなければなりません。した がって、スーパークラスでインスタンス変数へのアクセスメソッドが提供され ていない時など、どうしても必要な場合にのみ利用するべきです。

さらに、Rubyの場合にはより深刻な問題があります。それはインスタンス変数 名の衝突です。Rubyにはインスタンス変数の宣言がありませんから、サブクラ スで使用するインスタンス変数の名前が、たまたまスーパークラスのインスタ ンス変数と同じものだった場合、名前の衝突を自動的に検出する術がありませ ん。したがって、サブクラスでスーパークラスのインスタンス変数が書き換え られ、予期しない動作をしてしまいます。このような場合には、サブクラスで インスタンス名を変更して衝突を回避する必要があります。サブクラスはスー パークラスの実装にアクセスする権利と同時に、スーパークラスの実装を破壊 しない義務があるわけです。

しかし、この義務を果たすにはそれなりの労力が必要ですし、複数のモジュー ルをインクールドした際に、モジュールで使用しているインスタンス変数が衝 突してしまった場合には、モジュールを修正するか、どちらかのモジュールの 利用をあきらめるしかありません。

そこで、現在ruby-dev MLでは、プライベートインスタンス変数の導入が検討 されています。[3]*3 プライベートインスタンス変数はサブクラスか らもアクセスできないインスタンス変数です。サブクラスの同名のインスタン ス変数は違う変数とみなされます。これが導入されれば、名前の衝突の問題は 解決することになります。ただし、現時点ではまだどのような形になるのかわ かりませんし、導入されるとしても1.7以降のバージョンになるでしょう。

それまでの間は、プログラマの側で衝突に注意するしかありません。たとえば、 標準ライブラリのsync.rbなどでは、インスタンス変数に特定のプリフィック ス(sync_)を付けることによって、なるべく衝突が起こらないようにしています。

eRubyとは

さて、今回取り上げるのはeRubyです。eRubyは"Embedded Ruby"の意です、と 書くと、「組み込みシステム用のRubyか」と思われそうですが、そうではあり ません。テキストファイルにRubyのコードを埋め込むものです。たとえば、

1 + 1 = <% print 1 + 1 %>

のようなテキストをeRubyで処理すると、

1 + 1 = 2

のような出力を得ることができます。

eRubyがよく使われるのはCGIなどです。普通、RubyでCGIプログラムを書く場 合、Rubyプログラムの中にHTMLの断片(文字列)を埋め込んで、それらをプログ ラムで出力します。この方法はHTML全体の見通しが悪くなってしまうという問 題があります。

一方、eRubyを使う場合は、HTMLの中にRubyプログラムを埋め込み、埋め込ま れたプログラムの出力(動的に生成されたHTML)で、その部分を置き換えます。 このため、Rubyプログラムが埋め込まれた部分以外は普通のHTMLなので、HTML としての見通しがよくなります。また、プログラムの知識がない人にもデザイ ンの変更がしやすいというメリットもあります。

また、eRubyはHTMLだけでなく、あらゆるフォーマットに対応しています。(別 の言い方をすると、どのフォーマットに対しても特別な対応をしていません。) したがって、LaTeXやRDにRubyプログラムを埋め込む、といったことも可能で す。

eRubyの処理系

eRubyは特定のプログラムではなく言語(というとちょっとおおげさですが)の 名前です。eRubyの処理系(eRubyプロセッサ)は二種類あります。

eruby

eruby[4]はCによる実装で、Rubyインタプリタが組み込まれています。 erubyは一般的なフィルタコマンドと同じように動作し、コマンドライン引数 で指定されたeRubyファイル(指定がない場合は標準入力)を読み込み、処理結 果を標準出力に出力します。(Fig1) 環境変数GATEWAY_INTERFACEが設定されて いる場合には、erubyはCGIモードで動作します。CGIモードで動作する場合に は、最初にContent-Typeなどのへッダを出力してから、処理結果を出力します。

また、erubyは拡張ライブラリとして利用することもできます。

-- Fig1 erubyの実行例

  $ cat hello.txt
  <% print "hello world" %>
  $ eruby hello.txt
  hello world

ERb

ERbはeRubyのRubyによる実装で、Rubyが利用できる環境ならどこでも動作しま す。ERbにはフィルタとして動作するerb.rbと、CGIプログラムとして動作する erbcgi.rbが含まれています。

また、ERbもライブラリとして使うことができます。

eRubyの文法

Rubyプログラムの埋め込み

すでに例で出てきましたが、eRubyにRubyプログラムを埋め込むには次のよう にプログラムを<%と%>で括ってやります。

<% print "hello world" %>

eRubyプロセッサは<% ... %>ブロックをRubyプログラムとして評価(実行)し、 そのプログラムの出力結果で置き換えます。上の例をerubyやerb.rbで処理す ると、次のような出力結果が得られます。

hello world

この際、%>の後の改行はそのまま出力される点に注意してください。 たとえば、

<% print "hello world\n" %>
goodbye world

のようにprintで改行を出力すると、処理結果は、

hello world

goodbye world

のように空行を含むものになります。

ブロックは以下のように複数行にすることも可能です。

<%
print "hello world\n"
print "goodbye world\n"
%>

値の出力

先程の<% ... %>ブロックではprintなどを使って出力を行わないと何も処理結 果に出力されません。しかし、変数の値を出力したいような場合に、

Your name is <% print $name %>.

と記述しなければいけないのはなかなか面倒ですし、数が増えてくると読みや すさも損なわれます。

そんな時には<%= ... %>を使うことができます。eRubyプロセッサは<%= ... %> ブロックをRubyの式として評価し、そのブロックを式の値によって置き換えま す。たとえば、先程の例と同等の処理は、

Your name is <%= $name %>.

のように記述することができます。

<%= ... %>の中身は値を持つ式でなければなりません。たとえば、次のような プログラムはエラーになります。

<%= def foo; end %>

コメント

<% ... %>ブロックの内容はRubyプログラムとして扱われるので、当然Rubyの コメントも使うことができますが、eRubyにもコメント用の構文があります。 <%の直後に、#を記述すると、ブロック全体をコメントにすることができます。 コメントにされたブロックは出力からは削除されます。

<%#
これはコメントです。
%>

一行プログラム

%ではじまる行は行末までがRubyプログラムとして評価されます。<% ... %>と の違いは改行が出力されないことです。

example:
% x = 1 + 1
1 + 1 = <%= x %>

の処理結果は、

example:
1 + 1 = 2

となります。

ただし、この機能はERbでは利用できないので注意が必要です。

エスケープ

<%や%>という文字の並びそのものをテキストに記述したい場合、<%%や%%>のよ うに%を二つ続けて書くことでエスケープすることができます。

%%や<%%や%%>は特殊な意味を持つ。

の処理結果は、

%や<%や%>は特殊な意味を持つ。

のようになります。

erubyの仕組み

ここでerubyの仕組みについて簡単に説明します。

実はerubyはテキストに埋め込まれたRubyプログラムを一つ一つ実行しながら 置換していくわけではありません。erubyは、いったんeRubyファイルを通常の Rubyプログラムに変換し、そのRubyプログラムを実行する、という手順で eRubyファイルを処理します。たとえば、List1のようなeRubyファイルは、 List2のようなRubyスクリプトに変換された後、Rubyインタプリタによって実 行されます。

List2を見るとわかるように、テキストの地の部分はprintに変換され、 <% ... %>の部分はそのまま、<%= ... %>の部分は、

print(( ... ))

のように展開されます。

また、変換されたコードは元のeRubyファイルと行番号が一致するようになっ ています。(行番号は誌面用のもので実際の内容には含まれません。) これは エラーが発生した時に、エラー箇所を特定しやすくするためです。

erubyの内部的な仕組みなど知らなくても使えればいいじゃないかと思われる 方もいらっしゃるかもしれませんが、erubyの仕組み知っていると、List3のよ うな面白い使い方も理解できるようになります。List3はList4のようなRubyプ ログラムに変換されます。

なお、変換された後のRubyのコードを見るにはerubyの-vオプションを利用し ます。変換された後のコードは、実行時にエラーが起きた場合にも出力されま す。

-- List1 eRubyの例

   1: <%
   2: title = "test"
   3: %>
   4: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
   5: <html>
   6: <head>
   7: <title><%= title %></title>
   8: </head>
   9: <body>
  10: <h1><%= title %></h1>
  11: <p><% print "hello world" %></p>
  12: </body>
  12: </html>

-- List2 List1をRubyプログラムに変換したもの

   1: 
   2: title = "test"
   3: print "\n"
   4: print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
   5: print "<html>\n"
   6: print "<head>\n"
   7: print "<title>"; print(( title )); print "</title>\n"
   8: print "</head>\n"
   9: print "<body>\n"
  10: print "<h1>"; print(( title )); print "</h1>\n"
  11: print "<p>";  print "hello world" ; print "</p>\n"
  12: print "</body>\n"
  13: print "</html>\n"

-- List3 高度な(?)使い方

  <%
  functions = {
    "open" => "ファイルをオープンします。",
    "print" => "引数を順に出力します。",
    "printf" => "引数を順に出力します。",
    "proc" => "ブロックを手続きオブジェクト化します。",
  }	
  %>
  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  <html>
  <head><title>関数</title></head>
  <body>
  <h1>関数</h1>
  <dl>
  <% for func, desc in functions %>
  <dt><%= func %></dt>
  <dd><%= desc %></dd>
  <% end %>
  </dl>
  </body>
  </html>

-- List4 List3をRubyプログラムに変換したもの


  functions = {
    "open" => "ファイルをオープンします。",
    "print" => "引数を順に出力します。",
    "printf" => "引数を順に出力します。",
    "proc" => "ブロックを手続きオブジェクト化します。",
  }       
  print "\n"
  print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
  print "<html>\n"
  print "<head><title>関数</title></head>\n"
  print "<body>\n"
  print "<h1>関数</h1>\n"
  print "<dl>\n"
   for func, desc in functions ; print "\n"
  print "<dt>"; print(( func )); print "</dt>\n"
  print "<dd>"; print(( desc )); print "</dd>\n"
   end ; print "\n"
  print "</dl>\n"
  print "</body>\n"
  print "</html>\n"

インポート

eRubyを使っていると、eRubyファイルを複数のファイルに分割したくなること があります。そのような場合、erubyでは他のeRubyファイルをインポートする 機能を使うことができます。

インポートにはERubyというモジュールを利用します。ERubyはeRuby用のモジュー ルで、他にもいろいろな機能を提供しますが、ファイルのインポートには ERuby.importというメソッドを使います。List5ではbody.rhtmlというeRubyファ イルをインポートする例です。

実は、この機能も非常に単純な仕組みで実装されています。興味のある方は erubyのソースを覗いてみてください。

-- List5 インポートの例

  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  <html>
  <body>
  % ERuby.import("body.rhtml")
  </body>
  </html>

CGIとしての利用

eRubyはCGIで使うこともできます。eRubyをCGIで使うには主に二つの方法があ ります。

スタンドアロンCGI

一つはeRubyファイルそのものを実行可能なCGIプログラムとして利用する方法 です。通常のCGIと同じように#!行にerubyのパス(ERbの場合はerbcgi.rbのパ ス)を記述し、実行権限を与えるだけです。hello.cgiにアクセスすると、 erubyで処理された結果が出力されます。(先頭の#!行は出力されません。) へッダもerubyが出力してくれます。

ちょっと気を付けないといけないのが、$KCODEとcharsetの指定です。erubyは -Kオプションで$KCODEを、-Cオプションでへッダに出力されるcharsetを指定 することができます。ただし、RubyはJISをサポートしていないので、 -Kj -Ciso-2022-jpのような指定を行うことはできません。List6では$KCODEを eucに、charsetをeuc-jpに指定しています。 *4

他にcharsetを指定する方法としては、eRubyファイル中に、

<% ERuby.charset = "euc-jp" %>

のように記述する方法があります。

-- List6 hello.cgi

  #!/usr/local/bin/eruby -Ke -Ceuc-jp
  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  <html>
  <body>
  <p><% print "こんにちは" %></p>
  </body>
  </html>

PATH_INFOの利用

もう一つの方法は、eruby自体をCGIプログラムとして利用し、PATH_INFOで eRubyファイルを渡す方法です。たとえば、

http://your.host.name/cgi-bin/eruby

というURLでerubyにアクセスできる場合、

http://your.host.name/cgi-bin/eruby/~shugo/hello.rhtml

のようなURLでアクセスすると、/~shugo/hello.rhtmlがerubyに渡され、eRuby ファイルとして処理されます。

WWWサーバにApacheを利用している場合は、.htaccessなどにList7のように記 述することで、

http://your.host.name/~shugo/hello.rhtml

のようなURLでもアクセスできるようになります。

-- List7 Apacheの設定

  Action application/x-httpd-eruby /cgi-bin/eruby
  AddType application/x-httpd-eruby .rhtml

へッダ出力の抑制

erubyは通常へッダの出力を行いますが、時には自分でへッダを出力したい場 合もあります。そのような場合、List8のようにERuby.noheader=を使うことで、 へッダ出力を抑制することができます。

-- List8 へッダ出力の抑制

  <%
  require "cgi"

  ERuby.noheader = true
  cgi = CGI.new
  print cgi.header("type" => "text/plain")
  %>
  hello world

最後に

本当はerubyやERbをライブラリとして利用する方法も説明したかったのですが、 紙幅が尽きてしまいました。 erubyに関する質問はmodruby-ja ML[6]で受け付けていますので、疑問 のある方はmodruby-ja MLに参加してみてください。

次回はそのmod_rubyを取り上げたいと思います。

参考文献

[1]

Bertrand Meyer, 「オブジェクト指向入門」, 二木厚吉監訳,アスキー出版

[2]

H.R.マトゥラーナ/F.J.ヴァレラ, 「オートポイエーシス」, 河本英夫訳,国文社

[3]

[ruby-dev:13965], <URL:http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-dev/13965>

[4]

eruby, <URL:http://www.ruby-lang.org/en/raa-list.rhtml?name=eruby>

[5]

ERb - Tiny eRuby, <URL:http://www.ruby-lang.org/en/raa-list.rhtml?name=ERb+-+Tiny+eRuby>

[6]

modruby-ja ML, <URL:http://www.modruby.net/#label:9>


*1 オートポイエーシス[2]の説明などに 出て来そうです。
*2 instance_evalを使えば別ですが。
*3 みなさんお馴染みの後藤謙太郎さんのメールが きっかけになっています。
*4 通常#!行では複数のオプションは渡せないのですが、erubyがよしなに解 釈してくれます。