ここまで、Rubyのメタプログラミングを理解する上で欠かせない、「self」や特異メソッド、特異クラスといった仕組みについて解説しました。これは、メタなコードを書くためのメソッド(最初に紹介した「define_method」もそうです)を使ったコードを読み書きするために必須の知識です。
ここからは、より実践的な話として、数あるメタプログラミングのパターンのうち、簡単なものをいくつか紹介しましょう。
C#やJavaなどのオブジェクト指向言語を学んだ人を惑わせる概念の一つに、「オープンクラス」があります。オープンクラスとは、言語仕様として、クラスや継承やミックスインといった手続きなしに、既存のクラスを自在に拡張するようなコードを書けることを示します。また、「モンキーパッチ」とは、オープンクラスの仕組みを使うことで、既存のクラスの動作を上書きすることを言います。
ここで、モンキーパッチの例をpattern_01.rbに示します。その簡潔さと強力さと危険さに震えることでしょう。
class Fixnum def +(v) self - v end def *(v) self / v end end puts 42 + 42 puts 42 * 42
$ ruby pattern_01.rb 0 1
オープンクラスでクラスを拡張する場合は、クラスを定義する場合と同様、「class Fixnum」のように書きます。pattern_01.rbでは、整数を表す「Fixnum」クラスのメソッドをモンキーパッチしています。
3〜5行目は、「+」メソッドを上書きして、「self」で表された自分自身から、引数の「v」を減算したものを返しています。6〜8行目でも同様に、「*」メソッドを上書きして、除算した結果を返しています。
11行目では「+」演算子の結果を出力し、同様に12行目では「*」演算子の結果を出力しています。
これはモンキーパッチの強力さと危険さを端的に表現した例です。「Fixnum」を利用するプログラマー(Rubyプログラマーならほぼ全員でしょう)は「+」メソッドは加算を行うものだと期待して、「+」メソッドを利用します。しかしながら、モンキーパッチによって実際のところは減算が行われてしまうのです。
モンキーパッチは最後の手段だと思って、くれぐれも用法用量を守って、正しく使うことを心掛けましょう。
最後に、ちょっと込み入ったメタプログラミングの例を紹介しましょう。
最初に紹介した「define_method」による動的なメソッド定義を利用すると、似たような処理を持つメソッド群を、まとめて定義できます。また、「Object.const_set」「Object.cont_get」によって、動的にクラスを生成、取得できます。pattern_02.rbに、動的なメソッド定義とクラス操作の例を示しましょう。
{Cat: "meow", Dog: "woof", Owl: "hoot-hoot", Rabbit: "boo"}.each do |animal, roar| Object.const_set animal, Class.new Object.const_get(animal).class_eval do define_method :speak do |count| count.times { puts roar } end end end Cat.new.speak(2) Dog.new.speak(2) Owl.new.speak(2) Rabbit.new.speak(2)
$ ruby pattern_02.rb meow meow woof woof hoot-hoot hoot-hoot boo boo
さあ、少しややこしくなってきました。まずpattern_02.rbが行っていることを大ざっぱに言うと、『4つのクラス「Cat」「Dog」「Owl」「Rabbit」を動的に定義して、各クラスに対し、鳴き声を出力する「speak」というメソッドを定義する』ということを行っています。
1行目では、クラス名と鳴き声のペアを持つハッシュを作り、「each」メソッドで各ペアに対する処理を始めています。
2行目では、「Object.const_set」というメソッドを使って、「Object」クラスに各動物の名前(Cat、Dogなど)の名前を持つ定数に、クラスオブジェクトをセットしています。この動作によって、動的に新しいクラス(「Cat」「Dog」など)を定義できます。
4〜9行目では、「class_eval」メソッドを使って、各クラスに「speak」というメソッドを定義しています。例えば、「Hoge」というクラスがある場合に、「Hoge.class_eval」メソッドをブロックを渡して呼び出すと、このブロックの中は「Hoge」クラスの世界になります。ブロック内は「Hoge」クラスの世界なので、このブロックの中で定義したメソッドは「Hoge」クラスのオブジェクトが実行できるメソッドとなります。
また、4行目の「Object.const_get(animal)」により、例えば「Object.const_get(:Cat)」なら、「Cat」クラス自身を得ることができます。
5行目では「define_method」メソッドにより、「speak」というメソッドを定義しています。引数を許すメソッドを作る場合は、ブロック変数を利用します。ここでは、ブロック変数「count」が引数になります。
6行目では、引数として与えられた「count」回、動物の名前にひも付けられた鳴き声(「roar」)を画面に出力します。
このように、「define_method」「cont_set」「cont_get」「class_eval」を利用することで、動的なメソッド定義やクラス操作が可能となります。動物の種類が増えた場合でも、1行目のハッシュに項目を追加するだけで済みます。
簡潔かつ変化に強いコードとなった代償として、一見したときに、このコードが何をしているのか理解することが難しくなりました。抽象化においては、いつでも付きまとう問題です。その場合、適量のコメントを添えることも必要ですし、時にはメタプログラミングによる抽象化を諦める方がよい場合もあるでしょう。
ここまで、いくつかのメタプログラミングで使われるメソッドを紹介しましたが、他にもいろいろあります。代表的なものをいくつか挙げておきましょう。
今回は、Rubyにおけるメタプログラミングについて解説しました。数あるメタプログラミングのパターンを学ぶために重要な「self」、特異メソッド、特異クラスの説明から始まり、メタプログラミングのパターンの例を2つ紹介しました。
今回の解説で、いかにRubyがプログラマーに強力な道具を用意してくれていることが理解できたかと思います。数あるオブジェクト指向プログラミング言語の中でも、Rubyの強力さと自由度は群を抜いており、筆者がRubyを好む理由の一つです。興味を持たれた方は、参考書籍やWebを使って、さらなるRubyの深淵を覗いてみてください。
次回は総まとめとして、簡単なgem作成を通じた、Rubyによるアプリケーション開発について解説する予定です。お楽しみに!
麻田 優真(Rails技術者認定シルバー試験問題作成者)
イタリア、ローマ生まれ。中学生のころHSPに初めて触れ、プログラミングの楽しさを知る。オープンソースやハッカーカルチャーを好む。C#からRubyに転向したときに、動的型付け言語の柔軟性やメタプログラミングの魅力に感動し、Rubyとともにプログラマーとしての人生を歩む決意を固める。
現在は奈良先端科学技術大学院大学で学生として所属するかたわら、株式会社アジャイルウェアでプログラマーとして従事。Ruby on Railsによるコンシューマー向けのWebサービスの開発などに尽力している。
好きなメソッドは、define_method。
Twitter:@Mozamimy、ブログ:http://blog.quellencode.org/
山根 剛司(Ruby業務開発歴7年)
兵庫県生まれ。1997年からベンチャー系のパッケージベンダーで10年間勤務。当時、使用していた言語はJavaとサーバーサイドJavaScript。
2007年よりITコンサル会社に転職し、Rubyと出会って衝撃を受ける。基幹システムをRuby on Railsで置き換えるプロジェクトに従事。それ以来Ruby一筋で、Ruby on Railsのプラグインやgemも開発。
2013年より、株式会社アジャイルウェアに所属。アジャイルな手法で、Ruby on Railsを使って企業向けシステムを構築する業務に従事。
Ruby関西所属。
Twitter:@spring_kuma、Facebook:山根 剛司
Copyright © ITmedia, Inc. All Rights Reserved.