Rubyにpackage_privateを導入しようとしたら

Rubyのメソッドの可視性はpublic, protected, privateの3種類です。そこに同一パッケージ(イコール同一gem?)からのみ呼べるpackage_privateを導入してみましょう。

結論から言うと断念しました。

以下のような場合は簡単です。

class String
  def nya
    "#{self}にゃ"
  end

  package_private :nya
end

privateなどでもお馴染の引数付きでpackage_privateを呼んだ場合です。これは指定されたメソッドを同一パッケージから呼ばれたか判断するメソッドで包んで、同一パッケージからの呼び出し(ここではcallerで判断しました)じゃない場合はNoMethodErrorを投げる、でおっけー。

ではなぜ断念したか。以下のようなケースだと雲行きが怪しくなってきます。

class String
  package_private

  def nya
    "#{self}にゃ"
  end

  private

  def pyon
    "#{self}ぴょん"
  end
end

この場合、privateが呼ばれたら、package_privateを解除したいわけです。ってことはprivateの定義も上書きしなくちゃいけない?

で、本格的に断念したのが以下のケース。

class String
  package_private
end

class String
  def nya
    "#{self}にゃ"
  end
end

この場合のnyaメソッドはデフォルトの可視性であるpublicで定義されるべきなのですが、package_privateを解除する術が思い付かず、断念って感じです。ここらへんからはCの世界なので、CRubyのコードも読んでみたのですが、組み込むのはなかなかしんどそう。うむー。

追記(2013-04-16 08:10)

と、こんな記事を書いておいたら、これを読んだ@kyubingから神に等しきコードが飛んできました。

最初パッと見たところ、2度目のclass Nekoでpackage_privateが有効になりっぱなしにならないかな〜と思いましたが、そこらへんがRubyの妙のようで。

Rubyインスタンスメソッドが追加されると、Module#method_added(method_name)がコールされます。そのときのバックトレースは以下のような感じになるみたいです。

ファイル名:メソッドが定義された行:in `<class:クラス名>'
ファイル名:クラスの定義が開始された行:in `<main>' # mainかはクラスを定義した場所に依る

Class.newやclass_evalを使うともう少し違うバックトレースになりますが、それでも基本的に「何行目でクラスの定義が開始されたか」という情報は取れます。

と言うことは、完全に断念したというケースでも判断が付きそうですね! すごい! @kyubing様々です。また改めてコードにしてみようと思います。