ぎょーぼのぶろぐ

IT系の話を書いていくブログです。今はRubyの勉強中。

【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]

図にしてみる

このままだと分かりづらいので、図にすると下のようになっていました。

f:id:gyobo:20200802223927p:plain
クラス階層

どうなってるの?

  • 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