連載
» 2015年01月06日 20時15分 公開

若手エンジニア/初心者のためのRuby 2.1入門(12):難しいが強力! Rubyのメタプログラミング、self、特異クラス/メソッド、オープンクラスとモンキーパッチ (3/4)

[著:麻田優真、監修:山根剛司,株式会社アジャイルウェア]

オブジェクト固有のメソッド「特異メソッド」

 Rubyでは、特定のオブジェクトにのみ有効な、オブジェクト固有のメソッドを定義できます。通常、あるクラスのオブジェクトは、クラスに定義されているメソッドしか利用できません。しかしながら、「特異メソッド」という仕組みを使うと、オブジェクトにメソッドを追加できます。

 では、特異メソッドを定義する簡単な例をmeta_programming_05.rbに示します。

array = []
 
def array.append_randomized_number
  self << rand(10)
end
 
p array.respond_to?(:append_randomized_number)
p Array.new.respond_to?(:append_randomized_number)
 
10.times { array.append_rondomized_number }
p array
meta_programming_05.rb
$ ruby meta_programming_05.rb                                                                                                                     
true
false
[1, 6, 7, 6, 4, 9, 7, 7, 1, 3]
meta_programming_05.rbの実行例

 まず、1行目でarrayという変数に、空の配列であるArrayオブジェクトを格納しています。

 3〜5行目がポイントで、ここでarrayに対して特異メソッドを定義しています。特異メソッドを定義するときは、「def {オブジェクト}.{メソッド名}〜end」のように書きます。{オブジェクト}を省略した場合は、前節で説明した通り、「self」に対するメソッド定義となります。

 特異メソッドの定義中での「self」は、「array」そのものとなります。ですので、4行目のように「self」を使って「array」自身に、「rand」メソッドで生成した乱数を追加できます。

 7、8行目では、「respond_to?」メソッドを利用することで、オブジェクトに「append_randomized_number」が定義されているかを調べています。「respond_to?」は、レシーバーが引数の名前のメソッドを呼び出せるかを調べるためのメソッドです。

 実行結果の1行目および2行目から、「array」には特異メソッド「append_randomized_number」が定義されていますが、新しく生成した「Array」オブジェクトには定義されていないことが分かります。

 コードの10行目では、10回「append_randomized_number」を呼び出し、11行目でその結果を出力しています。

 このように、Rubyではオブジェクト固有のメソッドを定義できます。クラス自体にメソッドを定義する場合は、この仕組みを応用します。

クラスメソッドと「特異クラス」

 特異メソッドを理解できれば、クラス自体にメソッドを定義する所まで後一歩です。meta_programming_06.rbに、クラス自体にメソッドを定義する例を示しましょう。

class Rabbit; end
 
def Rabbit.colors
  [:black, :brown, :white, :mixed]
end
 
p Rabbit.colors
meta_programming_06.rb
$ ruby meta_programming_06.rb 
[:black, :brown, :white, :mixed]
meta_programming_06.rbの実行例

 1行目で、何もメソッドを定義しないような「Rabbit」クラスを定義しています。3〜5行目では、「Rabbit」クラスの特異メソッドとして、「colors」という名前のメソッドを定義しています。「colors」は、色を表すいくつかのシンボルを含んだ配列を返します。

 ここで、「Rabbit」クラスのオブジェクトではなく、「Rabbit」クラス自体(何度も言いますが、クラス自体もオブジェクトです!)に特異メソッドを定義していることに注意してください。Rubyでは、クラスメソッドを「クラスオブジェクトの特異メソッド」として定義できます。

 クラスメソッドを定義できたのはよいのですが、meta_programming_06.rbの書き方は少し不格好です。「Rabbit」クラスの宣言の中に書くのが自然ではないでしょうか。そこで、また「self」が生きてきます。meta_programming_07.rbに、「self」を利用したクラスメソッドの定義の例を示します。

class Rabbit
  def self.colors
    [:black, :brown, :white, :mixed]
  end
end
 
p Rabbit.colors
meta_programming_07.rb
$ ruby meta_programming_06.rb 
[:black, :brown, :white, :mixed]
meta_programming_07.rbの実行例

 1行目から「Rabbit」クラスの定義が始まり、2〜4行目でクラスメソッド「colors」を定義しています。ここで、「self」は「Rabbit」クラス自身を表していることを思い出してください。「self」の存在によって、エレガントにクラスメソッドを定義できるのです。

 もし、クラスメソッドが増えてきた場合、逐一「self.〜」と書くのも冗長な感じがします。そのような場合は、特異クラスという仕組みを使うとよいでしょう。特異クラスを使ったクラスメソッドの定義例を、meta_programming_08.rbに示します。

class Rabbit
  class << self
    def colors
      [:black, :brown, :white, :mixed]
    end
  end
end
 
p Rabbit.colors
meta_programming_08.rb
$ ruby meta_programming_08.rb 
[:black, :brown, :white, :mixed]
meta_programming_08.rbの実行例

 2行目に見慣れない記法「class << self」がありますが、これは、「self」(ここでは「Rabbit」クラス)の特異クラスを開きますよ」という宣言です。開いた特異クラスの中で定義したメソッドは、そのオブジェクトの特異メソッド、つまりここでは「Rabbit」クラス自体の特異メソッドとなります。そのため、meta_programming_07.rbと同様、「Rabbit.colors」で、色を表す配列を得ることができます。

self、特異メソッド、特異クラスについて説明した理由

 実際のメタプログラミングで利用されるメソッドや、そのパターンはさまざまです。メタプログラミングで使われるメソッドを理解するためには、「self」や特異メソッド、特異クラスについての知識が不可欠となります。そのため、今回はそれらの仕組みについて説明することにしました。

 また、クラスメソッドを定義することは、現場のコードでしばしば見られるパターンなのに、実際は複雑であることも理由の一つです。Ruby初心者からすると、「class << self」のようなコードを見たら、ギョッとすることでしょう(私もそうでした)。

 実際のメタプログラミングのパターンについては、書籍『メタプログラミングRuby』(アスキー・メディアワークス、Paolo Perrotta:著、角征典:訳)が参考になるでしょう。また、次節でもちょっとした簡単なパターンを紹介します。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。