前回の、Smalltalk の世界観に影響を受けた言語ではクラスもオブジェクトであらんとする…的な話の続きのような、そうでないような(^_^;)。
Java のクラス同様、Ruby のクラスもオブジェクトで、すべてのクラスは(やはり Java と同じ…)「Class」という名のクラスのインスタンスです。しかし、Ruby のクラスは Java のクラスよりオブジェクトとしては個性豊かで、クラス特異的な振る舞い“クラスメソッド”(Java の static 的なものではなく、Smalltalk でいうところの…)や属性“クラスインスタンス変数”(クラス変数や static 変数とは別に…)を定義して利用できます。これは、特異クラスという Java にはない特殊な仕組みにより実現されています。
特異クラスというのは、Ruby のオブジェクト(クラスに限らず…)において、インスタンス特異的なメソッド(特異メソッド)を定義するために便宜的に作られる匿名クラスのことです(特異クラス、特異メソッドともに Ruby ローカルな用語)。特異クラスは、もともと C 言語レベルでしか存在させないオブジェクトとして考えられていたせいか、通常の方法、たとえば、オブジェクトへの class メッセージの送信ではアクセスできないようになっています。
しかし、次のような特異クラスの定義式をイディオムとして用いることで、あるオブジェクト(obj)の特異クラスへのアクセスが可能になります。
class << obj; self end
これに適当な名前をつけて Object クラスのメソッドとして定義しておけば、クラスを含む任意のオブジェクトの特異クラスにアクセスする手段を得ることができます。
class Object def singleton_class class << self; self end end end
Object.class #=> Class String.class #=> Class Class.class #=> Class Object.singleton_class #=> #<Class:Object> String.singleton_class #=> #<Class:String> Class.singleton_class #=> #<Class:Class>
ただ、もともとが特異クラスの定義のための式であるという性格上、このメソッドには、特異クラスがないときにはそれを新たに生じさせてしまう…という副作用(?)が想定されます。通常のクラスには対応する特異クラスが必ず存在するので問題はないのですが、それ以外のオブジェクト(特異クラスを含む)では、観察したい状況に望まざる変化を生じさせてしまう可能性があるので注意が必要です。
ところで、Rubyソースコード完全解説 - 第4章 クラスとモジュール によれば、クラスの特異クラスは自身のクラスでもあるようです。このことが本当なら、自身に定義されたメソッドを自分に属するクラスからだけでなく、自らも起動できるはずです。
#通常のクラスへのメソッド定義では… class Foo; def m0; end end # メソッド「Foo#m0」の定義 Foo.instance_methods(false).include?("m0") #=> true Foo.new.m0 #=> nil -- 当然、起動できる。 Foo.m0 #=> NoNameError -- 当然、起動できない。
#特異クラスへのメソッド定義では… def Class.m1; end # Class クラスの特異クラスへのメソッド「m1」の定義 Class.singleton_class.instance_methods(false).include?("m1") #=> true Class.m1 #=> nil -- 当然、起動できる。 Class.singleton_class.m1 #=> nil -- 自身からも起動できる!
たしかにそう(クラスの特異クラスは自身のクラスでもある→自身に定義されたメソッドを起動できる…)なっています。…といいたいところですが、なぜだか、この振る舞いは例外的に Class クラスの特異クラス(#
def Foo.m2; end # Foo の特異クラスへのメソッド「m2」の定義 Foo.singleton_class.instance_methods(false).include?("m2") #=> true Foo.m2 #=> nil -- これは問題なし。 Foo.singleton_class.m2 #=> NoNameError -- !?
しかし、クラスの特異クラスの特異クラスについては、想定されるとおり自身に定義されたメソッドを起動できる…という不思議も。
def (Foo.singleton_class).m3; end Foo.singleton_class.m3 #=> nil -- 当然、起動できる。 Foo.singleton_class.singleton_class.m3 #=> nil -- !?
ところで、この m3 が、クラスの特異クラスの特異クラスに定義されたものであり、そのまた特異クラスに定義されたものではない(すなわち、間違いなく自身に定義されたメソッドを起動している)…ということを念のため確認しようとすると…、
Foo.singleton_class.singleton_class.m3 #=> nil Foo.singleton_class.singleton_class.instance_methods(false).include?("m3") #=> true Foo.singleton_class.singleton_class.singleton_class.instance_methods(false).include?("m3") #=> false Foo.singleton_class.singleton_class.m3 #=> NoNameError -- なにーっ!?
なんと、ついさっきまで起動できていた m3 がエラーになってしまいます。どうやら、先に述べた「副作用」による“状況の望まざる変化”が生じてしまっているようです。