トップレベル(モジュールやクラスの外)でメソッドを定義するとどうなるでしょうか??トップレベルでは、メソッドを定義するとデフォルトで 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クラスのインスタンスメソッドはクラスメソッドやモジュール関数のように見える、なんてことだびっくりだ!