Rubyの特異クラスと特異メソッドについて

/

特異メソッド

Rubyでは特定のオブジェクトに定義したメソッドを特異メソッドと呼びます。
この特異メソッドがどういったものなのか中々解りにくかったので調べてみました。

特異メソッドとはクラスではなくある特定のオブジェクトに固有の メソッドです。特異メソッドの定義はネストできます。

クラスの特異メソッドはそのサブクラスにも継承されます。言い替 えればクラスの特異メソッドは他のオブジェクト指向システムにお けるクラスメソッドの働きをすることになります。

クラスメソッドの定義/Ruby 2.3.0 リファレンスマニュアル

例えばRubyでは文字列はオブジェクトなのでこのように特異メソッドを定義出来ます。

var = "objcet"

def var.print
  puts self
end

var.print #object

クラス

クラスを特異メソッドの対象をにとる事も可能です。クラスの特異メソッドは特にクラスメソッドと呼ばれます。

Ruby におけるクラスメソッドとはクラスの特異メソッドのことです。Ruby で は、クラスもオブジェクトなので、普通のオブジェクトと同様に特異メソッド を定義できます。

クラスメソッドの定義/Ruby 2.3.0 リファレンスマニュアル

class Foo
  def self.greeting
    puts "Hello!!"
  end
end

Foo.greeting

selfを使ってクラス自身にメソッドを定義しています。

クラスメソッドは継承出来ます。

class Foo
  def self.greeting
    puts "Hello!!"
  end
end

class Bar < Foo
end

Bar.greeting #Hello!!

クラスメソッドはインスタンスメソッドと違ってクラスから直接呼び出せるので、呼び出す際の見た目はJavaのstaticメソッドようになります。そしてクラスメソッドはクラスに定義されたメソッドのため、そのクラスから作られたインスタンスから呼び出す事は出来ません。

class Foo
  def self.greeting
      puts "Hello!!"
  end
end

foo = Foo.new
foo.greeting #NoMethodError

モジュール

Rubyで扱える値は全てオブジェクトなので当然モジュールにも特異メソッドを定義することが出来ます。

module Foo
  def self.greeting
    puts "Hello!!"
  end
end

Foo.greeting #Hello!!

ただモジュールを対象とした特異メソッドをモジュールメソッドとはあまり呼ばないようです。

特異クラス

Rubyには特異メソッドの他に特異クラスと呼ばれるクラスも存在します。
しかしこの特異クラスについて、これはというオフィシャルの情報が出てこないので一体どういったクラスなのか良く分かりませんでした。
最初は単に特異メソッドが定義されているクラスの事かと思いましたが、どうもそうではないらいしい(それだとRubyの基本的なライブラリのクラスはほとんど特異クラスになってしまう)。

色々調べてみると参考になりそうなページが見つかったので引用します。

一方、Rubyの他の部分では「メソッドはクラスに紐づく」設計になっています。つまり、メソッドはクラスに定義され、クラスをnewしてできるインスタンスたちはメソッドを共有するという設計です。

「メソッドはクラスに紐づく」というRubyオブジェクト指向の基本原則を、特異メソッドというイレギュラーな存在と両立させるには、「すべてのオブジェクトは自分だけの隠しクラスを持つ*1」という仮定を追加してやることです。 そうすれば、特異メソッドは「自分だけのクラスに定義されたメソッド」と考えることができて(一応)筋が通ります*2。

この「自分だけのクラス」が特異クラスで、singleton_classメソッドで参照することが出来ます。

単にObjectをnewしただけのインスタンスでも #> という特異クラスを持っていることがわかります。 なおnilやfalseなどに対して singleton_class を呼んでも、通常のclass呼び出しと同じく NilClass、FalseClass が返るだけになります。

ちなみに、当初は正式な英語名が与えられていなかったことから”eigenclass”や”metaclass”などと呼ばれることもありますが、現在はコアライブラリのメソッド名になったこともあり”singleton class”でほぼ固まってきているようです

Rubyの特異クラス・特異メソッドについて/AllAbout デジタル

そして参照したページにも書いてありますがRubyの英語のドキュメントを調べてみると

The singleton class (also known as the metaclass or eigenclass) of an object is a class that holds methods for only that instance. You can access the singleton class of an object using class << object like this: ~
Singleton Classes/http://ruby-doc.org/core-2.3.3/doc/syntax/modules_and_classes_rdoc.html

特異クラスは英語では singleton, meta または eigen classと呼ばれているとあります(eigenは元はドイツ語だけど)。
それぞれの言葉の意味は

  • singleton(単体の)
  • meta(高次の)
  • eigen(独特の、自分自身の)

となるので、どうやら特異クラスとは特異メソッドを定義するときに参照されるオブジェクト自身を表すメタクラスの事のようです。

特異クラスがオブジェクトの持つ自身を表すメタクラスだとすると以下の書き方でクラスメソッドを定義出来る事が理解できます。

class Foo
end

class << Foo
  def greeting
    puts "Hello!!"
  end
end

#もしくは
class Foo
  class <<self
    def greeting
      puts "Hello!!"
    end
  end
end

Foo.greeting #Hello!!

もしこの書き方でFooクラスが参照してる対象が自身のメタクラス(特異クラス)ではないとしたら、参照にしているのが「自身そのもの」という事になるのでとても奇妙な文に見えてしまいます。

この場合奇妙というのはRubyの文法から見て「論理的に」おかしく見えるということで、コンピューターがRubyのプログラムを処理する手順とはまた別なのでしょうけれど、少なくともオブジェクトが自分を対象にとってメソッドを定義出来るのは”参照しているのがメタな自分だから”だと言われたほうがそうでない場合よりも納得出来ます。

そして実際にRubyのオブジェクトは自身と対応するメタクラスを持っているように振る舞います。
このメタクラスがRubyの通常のクラスとは違うクラスである事は、同じクラスから呼び出された異なるインスタンスの特異クラスを参照するとそれぞれ違った値を返すことからわかります。

class Foo
end

foo = Foo.new
foo2 = Foo.new

puts foo.singleton_class # #<Class:#<Foo:0x0055d313e40098>>
puts foo2.singleton_class # #<Class:#<Foo:0x0055d313e40070>>

参照しているクラスが違うのだからあるクラスから作られたインスタンスに定義した特異メソッドを、同じクラスから作られた別のインスタンスから参照する事は出来ません。

class Foo
end

foo = Foo.new
foo2 = Foo.new

def foo.greeting
  puts "Hello!!"
end

foo.greeting #Hello!!
foo2.greeting #NoMethodErrorになる

そしてインスタンスではなくクラスの特異クラスを確認した時に参照しているのもクラスそれ自身ではなくメタクラスであり、特異メソッドが定義される時にはこのメタクラス(特異クラス)が対象に取られます。

class Foo
end

puts Foo.singleton_class #<Class:Foo>

そう考えると特異クラスから見た場合はオブジェクトは特異クラスのインスタンスの様にも思えます。
それが(通常の意味での)クラスであってもそうで、参照した英語のドキュメントの定義にクラスとその特異クラスの関係を当てはめると(通常の意味での)クラスは定義の中でのInstanceに対応する事になります。

まとめ

僕のRubyと今のコンピューターに関する知識からすると話がかなり抽象的で難しく手に負えないので細かい分析はここら辺りでやめておきますが、解った事として

「特異メソッドはクラスではなく特定のオブジェクトに結び付けられたメソッドで、特異クラスとは各オブジェクトに紐付けられた特別なメタクラスである」

という事を押さえておけばとりあえずRubyのコードを書くには問題なさそうなので、もう少しRubyについて勉強したあとにもう一度振り返りたいと思います。