Skip to content

Instantly share code, notes, and snippets.

@sunaot
Last active July 4, 2019 02:32
Show Gist options
  • Star 26 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sunaot/8682795 to your computer and use it in GitHub Desktop.
Save sunaot/8682795 to your computer and use it in GitHub Desktop.
class << self によるクラスメソッドの定義のイディオムの説明とその背景にある特異クラスのかんたんな解説

Ruby のコードを読んでいると

class Hoge
  class << self
    def hello
      puts 'hello'
    end
  end
end

って出てきてびびるんだが、これはなんの暗号ですか?

こいつは特異クラスと言いまして、説明をすると大変長いものです。

まず、なにをしているかというと、これはクラスメソッドを定義するイディオムのひとつです。

http://docs.ruby-lang.org/ja/2.1.0/doc/spec=2fdef.html#class_method

def self.class_method 方式では複数のクラスメソッドをまとめて定義したい場合に都度の self. を書くのが面倒なため、以下の書き方でまとめてクラスメソッドを定義します (Rails がこの書き方を多用していたため、After Rails 世代の人はこうするものだと思ってる節がある。またこれを知らないと Rails 読むときの黒魔術ぽさが上がる)。

class Hoge
  class << self
    def a_class_method
    end

    def another_class_method
      # 二つ定義しても self つけなくていいから便利!
    end
  end
end

まずはこれだけわかれば Ruby を使ううえで困ることは少ないでしょう。

リファレンスマニュアルでの特異クラスの説明くらいは読んでみてもいいかもしれません。

しかし、実はこれだけではわかったようでなにもわかっていません。特異クラスについて、真剣に学ぶのであれば

あたりを読むのがよさそうです。さらに理解を深めたいなら、「初めての Ruby」、「プログラミング Ruby (ピッケル本)」、「パーフェクト Ruby」あたりの解説を読んでみるといいでしょう。

Ruby では (クラスではなく) オブジェクトに対して直接固有のメソッドを定義することができ、それを特異メソッドと読んでいます。

オブジェクトへ特異メソッドを定義するには下記のようにします。

hello = 'hello'

def hello.repeat(count = 1)
  count.times { print self }
end
hello.repeat 2 #=> hellohello

このとき、repeat メソッドは String クラスのオブジェクト 'hello' に対してのみ定義され、String クラスが拡張されたわけではありません。つまり、別の String クラスのオブジェクトへ 'hoge'.repeat(3) としても NoMethodError となります。

オブジェクトに特異メソッドを定義するにはもう一つ方法があり、オブジェクトの特異クラスをひきだして、直接特異メソッドを定義することができます。

hello = 'hello'

class << hello
  def world
    puts "#{self}, world"
  end
end
hello.world #=> hello, world

一見クラス定義に近い見た目ですが、これも << hello というところで hello オブジェクトの特異クラスを引き出しており、あくまでオブジェクトに対しての特異メソッドの定義となっています。

さて、ここでクラス定義のときの特異クラスの利用へ戻ると、

class Hoge
  class << self

としています。class 定義のコンテキストでの self とは、Class クラスのインスタンス Foo class です。class Foo とはクラスを定義するときの記法ですが、Ruby の中での理解としては、Class クラスのオブジェクトを生成し Foo というグローバルな定数へ代入しています。

つまり以下の二つはほぼ同義です。

class Foo
  def hello
    puts 'hello'
  end
end
foo = Foo.new
foo.hello #=> hello

Foo = Class.new do
  def hello
    puts 'hello'
  end
end
foo = Foo.new
foo.hello #=> hello

ここまでくるともう少し。クラスメソッド Foo.bye というのは、Foo に入っている Class クラスのオブジェクトへの特異メソッドの定義として読むことができます。

Foo = Class.new do
  def hello
    puts 'hello'
  end
end
def Foo.bye
  puts 'good bye'
end
Foo.new.hello #=> hello
Foo.bye       #=> good bye

これにオブジェクトの特異クラスの引き出しの記法 << をあわせて考えると、最初のイディオムがやろうとしていることがわかってきます。

# def Foo.bye の代わりに、特異クラスで特異メソッドを定義する
class << Foo
  def bye
    puts 'good bye'
  end
end

# 同じことをどうせならクラス定義の中でやってみる
# クラス定義の中で Foo に入ってるインスタンスを取るには self だよね!
class Foo
  class << self
    def bye
      puts 'good bye'
    end
  end
end

ということで、最初に読んだ形が出てきました。クラスメソッドのための記法があるわけではなく、特異メソッドという仕組みを使って巧みにクラスメソッドが実現されていることがわかりますね。

@sunaot
Copy link
Author

sunaot commented Oct 1, 2014

その後、るびまに多少丁寧にしたバージョンを公開しました。

http://magazine.rubyist.net/?0046-SingletonClassForBeginners

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment