トップレベル(モジュールやクラスの外)でメソッドを定義するとどうなるでしょうか??トップレベルでは、メソッドを定義するとデフォルトで private なメソッドになるので注意です。
01.#!/usr/bin/ruby 02. 03.p self.class # Object 04.p self # main 05. 06.cmethod = self.class.private_methods 07.imethod = self.private_methods 08. 09.def bar 10. puts "bar method" 11.end 12. 13.p self.class.private_methods - cmethod # ["bar"] 14.p self.private_methods - imethod # ["bar"]見ての通りですが、なぜかインスタンスメソッドとしてもクラスメソッドとしても利用できるようになっています。
これがちょっと気になったので調べてみました。どうもトップレベルで関数を定義すると Kernel モジュールに追加されるようです。
トップレベルでメソッドを定義したときの挙動が不思議 - (゚∀゚)o彡 sasata299's blog
またささたつさんが面白いことに気付いたっぽい。これ読んで俺も最初「えっ。なにそれこわい」と思ったんだけど、実はちょっと違うようです。ヒントはprivate_methodsの引数。
#!/usr/bin/env ruby cm = Object.private_methods mm = Kernel.private_methods im = self.private_methods def hoge end class Object private def piyo end end module Kernel def fuga end module_function :fuga end p Object.private_methods - cm #=> [:hoge, :piyo, :fuga] p Kernel.private_methods - mm #=> [:hoge, :piyo] p self.private_methods - im #=> [:hoge, :piyo, :fuga] p Object.private_methods(false) - cm #=> [] p Kernel.private_methods(false) - mm #=> [] p self.private_methods(false) - im #=> [:hoge, :piyo]
はい消えた。private_methodsの引数は「スーパークラスから継承したメソッドを含めるか否か」で、デフォルトはtrue。falseにすると消えるってことは、mainオブジェクトにとっては「そのクラスで定義されたメソッド」だけど、ObjectクラスやKernelモジュールにとっては「どっかから継承してきたメソッド」だということ。これが本当に「クラスメソッドとインスタンスメソッド両方作られてる」とか「Kernelモジュールのモジュール関数になってる」とかだったら、この結果にはならないはず。
private_methodsの引数以外で、元記事のコードと上のコードで違う部分がある。トップレベルでメソッドを作るのとObjectクラスのクラス定義の中でprivateなメソッドを定義したのは同じである、という認識でいたんだけど、実際のところObjectクラスで作ってみたらどうなるんだろう、と思ってやってみたのが上のコードのpiyoメソッド。で、やっぱりhogeとpiyoは同じ振る舞いをしているように見える。でもそれはいいとして、やっぱり上のコードではトップレベルのメソッドだけじゃなくて、Objectクラスのクラスメソッドまでできてしまっているように見える。これはなんだろう。
"クラス"って何者?
ところで、さっきからクラスクラスと言ってるけど、Rubyの用語で気を付けなきゃいけないことがある。それはOOPの用語としての"クラス"と"Classクラス"は別者ってこと。俺がカタカナで"クラス"と書いてるときは、普通にOOP的な概念でのクラスのことを言ってるんだけど、上のコード中の"Object"はクラス定義そのものではなくて、実態は「"Classクラスのインスタンス"を作り、それを"Object"という定数にしたもの」だ。初めてのRubyを持ってる方は、8章のどこかにあるコラムを探してみて欲しい。小さい枠だけど、ちゃんと「クラス名とはClassクラスのインスタンスを作り、それを定数にしたもの」って話が書いてある。
で、Rubyのクラスのツリーを見て欲しい。"Object"はClassクラスの、"Kernel"はModuleクラスのインスタンスなんだけど、ClassクラスはModuleクラスの、ModuleクラスはObjectクラスのサブクラスになっている。つまり、上のコード中では"self"も"Object"も"Kernel"も、みんなObjectクラス(か、そのサブクラス)のインスタンスと言える。だから、Objectクラスにインスタンスメソッドを追加したつもり(実際その通りなんだけど)なのに、あたかもクラスメソッドが追加されたように見える。でも、"Object"だって"インスタンス"なんだもの、インスタンスメソッドが使えて何もおかしくないじゃない、という話、か。
ということで、最初のコードの謎が解ける。トップレベルのself、つまりmainオブジェクトは、Objectクラスそのもののインスタンスなので、private_methods(false)をするとちゃんとObjectクラスで定義したメソッドを出してくれる。で、"Object"や"Kernel"はObjectクラスのサブクラスのインスタンスなので、private_methods(false)するとObjectクラスのメソッドが消える。ということでした。
まとめ
- トップレベル関数はObjectクラスのprivateなインスタンスメソッド(と、同じに見える、多分同じ)
- クラス名やモジュール名の実態はClassクラスやModuleクラスのインスタンスであり、単なる定数
- ClassクラスもModuleクラスも元を辿ればObjectクラス
- Objectクラスのインスタンスメソッドはクラスメソッドやモジュール関数のように見える、なんてことだびっくりだ!