【Ruby v2.6】クラス階層について調べてみる
前置き
O'Reilly の「プログラミング言語 Ruby」「メタプログラミング Ruby」を一通り読みました。
が、読んでると「なるほどー」と思って理解したつもりになっても、少しややこしいソースを見たりするとすぐにわからなくなる、、、「Classクラスが云々」「実はクラスメソッドは特異クラスで・・・」「クラスもオブジェクト、インスタンスが、、、」等々。頭の中がこんがらがってきます。
なので、頭の中の整理のために、少しずつまとめて書いていきたいと思っています。
※ あくまで、自分の理解の範囲で書いてます。間違ったことも書いてしまうと思いますが、ご指摘いただければ修正します。
クラス階層について調べてみた
Ruby の組み込みクラスはたくさんありますが、代表的なクラスとしては、BasicObject, Object, Class, Module があり、モジュールとしては Kernel があります。Ruby のクラス階層を理解するためにの、それぞれのクラスに対してclass
, superclass
, singleton_class
, ancestors
を使って調べてみます。
Class.class #=> クラスを返す。 Class.superclass #=> の継承元クラスを返す。 Class.singleton_class #=> クラスの特異クラスを返す。 Class.ancestors #=> クラスの継承をルートまで逆順で返す。
メソッドを使って階層を調べてみる
これを、BasicObject, Object, Module, Class, Kernel と、それぞれの特異クラス(singleton_class)に対して実行した結果を見てみると。
BasicObject (Class) --------------------------------------- BasicObject.superclass #=> BasicObject.singleton_class #=> #<Class:BasicObject> BasicObject.ancestors #=> [BasicObject] Object (Class) -------------------------------------------- Object.superclass #=> BasicObject Object.singleton_class #=> #<Class:Object> Object.ancestors #=> [Object, Kernel, BasicObject] Kernel (Module) ------------------------------------------- Kernel.superclass #=> NoMethodError Kernel.singleton_class #=> #<Class:Kernel> Kernel.ancestors #=> [Kernel] Module (Class) -------------------------------------------- Module.superclass #=> Object Module.singleton_class #=> #<Class:Module> Module.ancestors #=> [Module, Object, Kernel, BasicObject] Class (Class) --------------------------------------------- Class.superclass #=> Module Class.singleton_class #=> #<Class:Class> Class.ancestors #=> [Class, Module, Object, Kernel, BasicObject] #<Class:BasicObject> (Class) ------------------------------ BasicObject.singleton_class.superclass #=> Class BasicObject.singleton_class.singleton_class #=> #<Class:#<Class:BasicObject>> BasicObject.singleton_class.ancestors #=> [#<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject] #<Class:Object> (Class) ----------------------------------- Object.singleton_class.superclass #=> #<Class:BasicObject> Object.singleton_class.singleton_class #=> #<Class:#<Class:Object>> Object.singleton_class.ancestors #=> [#<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject] #<Class:Kernel> (Class) ----------------------------------- Kernel.singleton_class.superclass #=> Module Kernel.singleton_class.singleton_class #=> #<Class:#<Class:Kernel>> Kernel.singleton_class.ancestors #=> [#<Class:Kernel>, Module, Object, Kernel, BasicObject] #<Class:Module> (Class) ----------------------------------- Module.singleton_class.superclass #=> #<Class:Object> Module.singleton_class.singleton_class #=> #<Class:#<Class:Module>> Module.singleton_class.ancestors #=> [#<Class:Module>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject] #<Class:Class> (Class) ------------------------------------ Class.singleton_class.superclass #=> #<Class:Module> Class.singleton_class.singleton_class #=> #<Class:#<Class:Class>> Class.singleton_class.ancestors #=> [#<Class:Class>, #<Class:Module>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
図にしてみる
このままだと分かりづらいので、図にすると下のようになっていました。
どうなってるの?
- BasicObject クラスが全てのクラスのルートとなっている(BasicObject クラスの親クラスは nil )。
- 意外だったのが、BasicObject クラスの特異クラス。このクラスの親クラスは Class クラスになっています。これによって、特異クラスについても superclass を追っていくと、最後は必ず BasicObject に辿り着くようになっています。
- Kernel モジュールは、Object クラスにインクルードされているので、Object クラスの親クラスは BasicObject クラスですが、ancestors メソッドでは、BasicObject と Object の間に入ってきます。しかし、Kernel モジュールの特異クラスは、ancestors でも階層からは外れていて、Module クラスのサブクラスになっています。
確認のために作成したコード
最初は irb で一つひとつメソッドを確認していたのですが、途中で面倒くさくなってしまったので、拙いですが確認用のコードを作りました。
大したことはさせてません。結果を見やすくするのに少し時間かけたくらいです。
- ターゲットとなるクラスを配列で生成。そこからそれぞれの特異クラスを作成し、配列に追加。
- 実行するメソッドも、配列で持たせてます。
- それぞれの配列を .each メソッドで回し、send メソッドで実行した結果を出力してます。
- 今回のターゲットクラスの中で、Kernel のみモジュールなので、superclass メソッドで NoMethodError 例外が発生します。ですので、メソッドの有無を respond_to? メソッドで確認し、例外を回避しています。
以下、コードです。
# 情報表示用クラス class ShowClassInfo # 初期化 def initialize @target_class = [BasicObject, Object, Kernel, Module, Class] @target_method = ["superclass", "singleton_class", "ancestors"] sgt = [] @target_class.each { |tgt| sgt.push(tgt.singleton_class) } @target_class += sgt end # 情報表示メソッド def show_info @target_class.each do |tgt| puts "#{tgt.to_s} (#{tgt.class}) ".ljust(60, "-") # クラス毎のヘッダー @target_method.each do |mtd| puts get_callmethod_to_show(tgt, mtd) << get_callmethod_response_to_show(tgt, mtd) end puts "" # クラス毎のフッター(一行空ける) end end private # 結果の整形用クラス # 特異クラスのクラス名文字列を .singleton_class 呼び出し文字列に変更し、表示用に整形も行う。 def get_callmethod_to_show(klass, method) "#{klass.to_s.gsub('#<Class:','').delete('>')}" << ( klass.singleton_class? ? ".singleton_class" : "" ) << ".#{method}".ljust(20) end # メソッド呼び出し結果の整形 def get_callmethod_response_to_show(klass, method) "#=> " << ( klass.respond_to?(method) ? klass.send(method).to_s : "NoMethodError" ) end end # 実行 hoge = ShowClassInfo.new hoge.show_info