【一人 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 のインスタンスメソッドとしても定義される
ここが重要ぽいんですが、先ほどのようにトップレベルで定義したメソッドは『Object の private なインスタンスメソッド』としても定義されます。
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 homu と def 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 のソースコードを読んでみないとわかりませんが、認識としてはこっちの方がわかりやすい…。