【一人 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 のソースコードを読んでみないとわかりませんが、認識としてはこっちの方がわかりやすい…。