表題の通り。
今日Pry触ってて気づいたんだけど、Pryのトップレベルで定義したメソッドはObjectクラスのpublicなインスタンスメソッドになるっぽくて、大抵のオブジェクトから呼び出せる様になる。意図した仕様なのかバグなのかは分からないけど、びっくりしたからメモっとく。
ちなみにRubyのversionは2.1.5。Pryは普通にgem installした。irbやRubyを普通に起動した場合には挙動が違ってて、privateなインスタンスメソッドになる。
$ ruby -v
ruby 2.1.5p273 (2014-11-13 revision 48405) [x86_64-darwin14.0]
$ pry -v
Pry version 0.10.1 on Ruby 2.1.5
何に驚いたのか?
以下にコードで例を示す。
[1] pry(main)> def my_method
[1] pry(main)* p 'my_method'
[1] pry(main)* end
=> :my_method
[2] pry(main)> [].my_method // arrayオブジェクトのメソッドとしてmy_methodが呼び出せる!!
"my_method"
=> "my_method"
[3] pry(main)> [].methods.include?(:my_method) // arrayオブジェクトの呼び出し可能なメソッドにmy_methodが含まれてる!!
=> true
トップレベルで定義したmy_method
メソッドが、空のarrayオブジェクトである[]
から呼び出せている。割と驚きの挙動。
なぜこうなるのか?
表題にも書いたけど、Object
クラスのインスタンスメソッドとしてmy_method
が定義されてるのが原因。Objectクラスはほとんどのオブジェクトの上位クラスに位置しているので、大抵のオブジェクトからmy_method
が呼び出せるようになる。
[4] pry(main)> Object.instance_methods(false).include?(:my_method) // instance_methods(false)で、そのクラスで定義されたinstance_methodsが取得できる
=> true
[5] pry(main)> [].class
=> Array
[6] pry(main)> [].class.superclass // Arrayの上位クラスにObjectが位置している
=> Object
自分で適当に定義したクラスもObjectクラスを継承した状態になるので、そこから生成したオブジェクトもmy_method
を呼び出せる。
[7] pry(main)> class MyClass; end
=> nil
[8] pry(main)> my_obj = MyClass.new
=> #<MyClass:0x007fbf6972ce20>
[9] pry(main)> my_obj.my_method
"my_method"
=> "my_method"
Objectよりもさらに上位のBaicObjectを継承すれば、こうはならない。
[10] pry(main)> class MyBasicClass < BasicObject; end
=> nil
[11] pry(main)> my_basic_obj = MyBasicClass.new
=> #<MyBasicClass:0x3fdfb62dab38>
[12] pry(main)> my_basic_obj.my_method
NoMethodError: undefined method `my_method' for #<MyBasicClass:0x007fbf6c5b5670>
from (pry):12:in `__pry__'
ここまでがPryでの話。
irbやRubyコマンドで起動した場合はどうなるか
Objectのprivateなインスタンスメソッドとして定義されるので、外部から呼び出しは出来ない。
irb(main):001:0> def my_method
irb(main):002:1> p 'my_method'
irb(main):003:1> end
=> :my_method
irb(main):004:0> [].my_method
NoMethodError: private method `my_method' called for []:Array
from (irb):4
from /Users/(ユーザ名)//.rbenv/versions/2.1.5/bin/irb:11:in `<main>'
じゃあ何でこんな挙動(わざわざObjectクラスにprivateなインスタンスメソッドとして定義)になってるかと言うと、オブジェクトのメソッドを定義する時なんかにトップレベルで定義されたメソッドを使えるようにする為。
irb(main):005:0> class MyClass
irb(main):006:1> def try_2_my_method
irb(main):007:2> my_method // my_methodが使える!!
irb(main):008:2> my_method // my_methodが使える!!
irb(main):009:2> end
irb(main):010:1> end
=> :try_2_my_method
irb(main):011:0> my_obj = MyClass.new
=> #<MyClass:0x007fefdb285938>
irb(main):012:0> my_obj.try_2_my_method
"my_method"
"my_method"
=> "my_method"
要は、Rubyではいわゆるグローバルスコープ的な機能を提供する為にObjectクラスを利用してるっぽい。
この事は、BaiscObjectを継承するクラスの中でインスタンスメソッドを定義する時にはトップレベルで定義したメソッドが使えない事からも確認できる。
irb(main):013:0> class MyBasicClass < BasicObject
irb(main):014:1> def try_3_my_method
irb(main):015:2> my_method
irb(main):016:2> my_method
irb(main):017:2> my_method
irb(main):018:2> end
irb(main):019:1> end
=> :try_3_my_method
irb(main):020:0> my_basic_obj = MyBasicClass.new
(Object doesn't support #inspect)
=>
irb(main):021:0> my_basic_obj.try_3_my_method
NameError: undefined local variable or method `my_method' for #<MyBasicClass:0x007fefdc86dc50>
from (irb):15:in `try_3_my_method'
from (irb):21
from /Users/(ユーザ名)/.rbenv/versions/2.1.5/bin/irb:11:in `<main>'
try_3_my_method
メソッドの定義の中でmy_method
を呼び出そうとしてエラーが出てる事が分かる。my_method
はグローバルに定義されてる訳では無くて、あくまでObject
クラスのインスタンスメソッドとして定義されてる事が確認出来たと言える。
まとめ
Pryの驚きの挙動から始めて、Rubyのメソッドスコープの仕組みについての知見を得た。あとどうでも良いかもだけど、methods
とかinstance_methods
とかprivate_instance_methods
とかsingleton_methods
みたいな定義されたメソッドをarrayで取得する系のメソッドが今回かなり役立ったので、使っていくと良いと思う。特に、falseを引数として渡すと上位クラス由来のものは取り除いてくれるので、どこでメソッドが定義されてるかが特定しやすかった。