Ruby 初級者のための class << self の話 (または特異クラスとメタクラス)
はじめに
この記事は「最近 Ruby を始めたばかりで言語仕様についてよく知らない」という初級者や、「一通り記法は知っていて Ruby でプログラミングはできるが、その仕組みはよくわからない」という中級者へ向けて書いています。
Ruby を始めるならどんな本を読めばいいの? とたびたび聞かれます。そんなときは決まって、「他の言語が使えるなら『はじめての Ruby』が鉄板で、あとは実際に使いながらリファレンスマニュアルをなるべく参照するといいですよ」と答えます。
そうして実際に自分で Ruby のコードを読み書きし始めた人が、決まって「あれ?」と立ち止まるところがあります。今回はその中の一つ class << self の話です*1。
リファレンスマニュアルへの参照など、より深く学ぶためのリンクもありますので、記事を読んでその仕組みに興味をもった人が理解を深めるきっかけになってくれれば執筆者冥利につきます。
class << self って?
class << self に出会うとき
Ruby のコードを読んでいると、
class Foo class << self def hello puts 'hello' end end end
という表記に出会うことがあります。この class << self という謎めいた表記はいったい何なのでしょうか?
この class << self は特異クラスと言いまして、説明をすると大変長いものです。
まず、なにをしているかというと、これはクラスメソッドを定義するイディオムのひとつです。マニュアルにも記載があります。クラスメソッドの定義の特異クラス方式と書かれている箇所です。
クラスメソッドの定義の仕方
Ruby でのクラスメソッドの定義の仕方には大きくわけて二つのやり方があります。一つは特異メソッド方式、もう一つが特異クラス方式です。
- def self.method_name の特異メソッド形式
- 特異メソッド方式では、def self.class_method のようにメソッド名の前にクラスメソッドを定義する対象のクラス名をつけて定義します。リファレンスマニュアルにあるように直接クラス名を書いてもいいのですが、定義するクラス自身の中に書く場合はこのように self. と書くことが多いでしょう。
- class << self の特異クラス形式
- 特異クラス方式では、class << self と書いた行から end までの間に def class_method のようにクラス名を書かずにインスタンスメソッドと同じようなメソッド定義を書いていきます。この間に書いたものはクラスメソッドとして定義されます。
どちらも正しいクラスメソッドの定義の仕方ですが、特異メソッド方式では複数のクラスメソッドをまとめて定義したい場合に都度の self. を書くのが面倒なため、そのようなときは特異クラス方式がとられることが多いようです。まずはこれだけわかれば Ruby を使う上で困ることは少ないでしょう。
特異クラス方式についてだけ、少し説明を補足します。まずはコードの例を見てみましょう。
# 特異クラスによるクラスメソッドの定義例 class Foo class << self def a_class_method end def another_class_method # 二つ定義しても self つけなくていいから便利! end end end
リファレンスマニュアルでの特異クラスの説明くらいは読んでみてもいいかもしれません。
しかし、実はこれだけではわかったようでなにもわかっていません。特異クラスについて真剣に学ぶのであれば、初めての Ruby の著者でもある yugui さんの書いたRuby のメタクラス階層あたりを読むのがよさそうです。さらに理解を深めたいなら、「プログラミング Ruby (ピッケル本)」、「パーフェクト Ruby」、「メタプログラミング Ruby」あたりの解説を読んでみるといいでしょう。
かつては Rails が class << self な特異クラス方式の書き方を多用していたため、After Rails の gem には特異クラス方式が流行っていたという印象があったのですが、執筆時点最新の Rails (ver4 系) を読むと特異メソッド方式と extend によるクラスメソッドの読み込みへ大部分が変わっていました。おかげでコードの見通しが非常によくなっています。これは学ぶべき点が多いですね。
class << self の仕組みを順に理解する
さて、日常の Ruby コードで class << self を利用するための説明はここまでで終わりです。ここからは、実際に特異クラスやメタクラスを実感するために順を追って動きを見てみましょう。
特異メソッドの定義の仕方
def object.method_name 形式
Ruby では (クラスではなく) オブジェクトに対して直接固有のメソッドを定義することができ、それを特異メソッドと呼んでいます。
オブジェクトへ特異メソッドを定義するには下記のようにします。
hello = 'hello' def hello.say(count = 1) count.times { print self } end hello.say 2 #=> hellohello another_hello = 'hello' another_hello.say 3 #=> NoMethodError: undefined method `say' for "hello":String
このとき、say メソッドは String クラスのオブジェクト 'hello' に対してのみ定義され、String クラスが拡張されたわけではありません。つまり、別の String クラスのオブジェクトへ another_hello.say(3) としても NoMethodError となります。
class << object 形式
オブジェクトに特異メソッドを定義するにはもう一つ方法があり、オブジェクトの特異クラスをひきだして、直接特異メソッドを定義することができます。
hello = 'hello' class << hello def say_world puts "#{self}, world" end end hello.say_world #=> hello, world
一見クラス定義に近い見た目ですが、これも << hello というところで hello オブジェクトの特異クラスを引き出しており、あくまでオブジェクトに対しての特異メソッドの定義となっています。
class << self を解釈してみる
さて、ここでクラス定義のときの特異クラスの利用へ戻ると、
class Foo class << self
としています。クラス定義のコンテキストでの self とは、Class クラスのインスタンス Foo class です。class Foo とはクラスを定義するときの記法ですが、Ruby の中での理解としては、Class クラスのオブジェクトを生成し Foo というグローバルな定数へ代入しています。
つまり以下の二つはほぼ同義です。
クラス定義のシンタクス class を使って Bar クラスを定義する場合。
class Bar def hello puts 'hello' end end bar = Bar.new bar.hello #=> hello
Class クラスのインスタンスを生成して、定数 Bar へ束縛する場合。
Bar = Class.new do def hello puts 'hello' end end bar = Bar.new bar.hello #=> hello
ここまでくるともう少しです。
定数 Bar に入っている Class クラスのオブジェクトへ特異メソッドを定義してみましょう。特異メソッドの定義の仕方は def object.method_name という形式でした。Bar のオブジェクトに特異メソッドを定義するので、def Bar.method_name と定義することになります。
Bar = Class.new do def hello puts 'hello' end end def Bar.bye puts 'good bye' end Bar.new.hello #=> hello Bar.bye #=> good bye
Bar.bye という呼出しができました。これはクラスメソッドの呼出しと同じですね。つまり、クラスメソッド Bar.bye というのは、Bar に入っている Class クラスのオブジェクトへの特異メソッドの定義として読むことができます。
これにオブジェクトの特異クラスの引き出しの記法 << をあわせて考えると、最初のイディオムがやろうとしていることがわかってきます。
クラスメソッドの定義は特異メソッド形式と特異クラス形式があったのでした。特異メソッド形式で定義した def Bar.bye を特異クラス形式へ書きかえてみます。
Bar = Class.new do def hello puts 'hello' end end class << Bar def bye puts 'good bye' end end Bar.new.hello #=> hello Bar.bye #=> good bye
できました。せっかくなので、クラス定義をすべて class 記法での宣言の中に入れてみましょう。クラス定義の中で Bar に入ってるインスタンスを取得するには self を使うのでした。
class Bar def hello puts 'hello' end class << self def bye puts 'good bye' end end end Bar.new.hello #=> hello Bar.bye #=> good bye
ここでようやく最初に読んだ形が出てきました。クラスメソッドのための記法があるわけではなく、特異メソッドという仕組みを使って巧みにクラスメソッドが実現されていることがわかりますね。
まとめ
Ruby で特異クラスと特異メソッドの記法を使ってメタクラス*2へアクセスし、クラスメソッドとして働くメソッド定義ができることを順に見てきました。ふだん何気なく使っているものや読んでいるコードの仕組みを調べるおもしろさが少しでも伝わったなら、この記事の目論みは成功です。Enjoy programming!! :)
著者について
たなべすなお (Sunao Tanabe / @sunaot)
日本 Ruby の会、Asakusa.rb 所属 (先日ひさしぶりに参加した)。Rubyist Magazine 編集者。記事公開時点では Perl の会社ですっかり Ruby の仕事が多くなってきたプログラマ。sunaot@github | sunaot@twitter
キーワード:
参照:[Rubyist Magazine 0046 号] [0046 号 巻頭言] [Rubyist Magazine 十周年] [0048 号 アクセスランキング] [0049 号 アクセスランキング] [分野別目次] [各号目次]