【一人 Ruby Advent Calendar 2017】トップレベルメソッドの扱い【18日目】

一人 Ruby Advent Calendar 2017 18日目の記事になります。
Ruby でトップレベルに定義したメソッドがどういう扱いなのかイマイチわからなかったので調べてみた。

トップレベルの self

まず、トップレベルの self ですが、これは main という特別なオブジェクトになります。

p self
# => main

また、main という名前では参照することは出来ません。
トップレベルで定義したメソッドはこの main オプジェクトの private メソッドとして定義されます。

def homu
    "homu"
end

p self.private_methods(false)
# => [:include, :using, :public, :private, :define_method, :DelegateClass, :homu]

# Error: private method `homu' called for main:Object (NoMethodError)
# p self.homu

p self.send :homu
# => "homu"

main で定義されたメソッドは Objectインスタンスメソッドとしても定義される

ここが重要ぽいんですが、先ほどのようにトップレベルで定義したメソッドは『Objectprivateインスタンスメソッド』としても定義されます。

def homu
    "homu"
end

p Object.private_instance_methods(false)
# => [:DelegateClass, :homu]

Objectインスタンスメソッドとして定義されているので『Object を継承しているクラス(つまり殆どのオブジェクト)』からトップレベルのメソッドを呼び出すことが出来ます。

def homu
    "homu"
end

class X
    # Object#homu なのでどこからでも呼べる
    p homu
    #x=> "homu"

    def mado
        homu + homu
    end
end

p X.new.mado
# => "homuhomu"

# ただし、private メソッドなのでレシーバを付けた呼び出しは出来ない
# Error: private method `homu' called for #<X:0x0000000000ae12d0> (NoMethodError)
# p X.new.homu

ちなみに以下のように BasicObject を継承している場合は Object は継承していないので使用することは出来ません。

def homu
    "homu"
end

class X < BasicObject
    def mado
        # Error: undefined local variable or method `homu' for #<X:0x00000000021fd720> (NameError)
        homu + homu
    end
end

p X.new.mado

トップレベルで def homudef self.homu した場合の違い

余談ですが、トップレベルでメソッドを定義した場合と self に対して特異メソッドを定義した場合の追加です。

def self.singleton_method_added name
    return if name == :singleton_method_added
    p name
end

# self.singleton_method_added は呼ばれない
def homu
end

# private メソッドとして追加される
p Object.private_instance_methods(false)
p self.private_methods(false)


# self.singleton_method_added は呼ばれる
def self.mami
end

# Object には追加されない
p Object.instance_methods(false)

# self の public メソッドに定義される
p self.methods(false)

トップレベルでメソッドを定義した場合と self の特異メソッドとして定義した場合では挙動がかなり違います。

そもそもトップレベルのメソッドは main ではなくて Object に定義されるのでは…?

トップレベルで定義したメソッドは『main オプジェクトの private メソッド』として定義されると思っていたんですが、そもそも『main に定義される』のではなくて『Objectに定義される』という方が正しいのではないでしょうか。 これにより『Object にメソッドが定義されること』で結果的に『main でも使用することが出来る』という挙動の方が個人的には自然な気がします。
このあたりは Rubyソースコードを読んでみないとわかりませんが、認識としてはこっちの方がわかりやすい…。

まとめ

  • ×トップレベルのメソッドは mainprivate メソッドとして定義される
  • ○トップレベルのメソッドは Objectprivate メソッドとして定義される
    • なので結果的に main にもメソッドが追加される
  • トップレベルの self(main)とトップレベルで定義するメソッドは直接的な関係性はない(と思う)
  • Object クラスはほぼ全てのオブジェクトの継承リストに追加されている
    • なので Object クラスで定義されたメソッドはほぼ全てのオブジェクトから使える


Ruby のトップレベルの扱いむずかしい…。