Ruby の Prime がつよい

いろいろあって Prime クラスのソースを読んでいたらとても勉強になったよ。

Prime

  • 素数全体を表すクラスだよ
  • 素数全体というオブジェクトは1つしか存在しえないので、シングルトンになるよ
  • 利便性のため、デフォルトインスタンスのメソッドをクラスメソッドとしても使用できるよ
  • Prime.new で Ruby 1.8 の Prime の互換クラスのインスタンスを生成できるよ

ソースをよむ

lib/prime.rb から必要部分を抜粋するよ。

require "singleton"
require "forwardable"

class Prime
  include Enumerable
  @the_instance = Prime.new
 
  # obsolete. Use +Prime+::+instance+ or class methods of +Prime+.
  def initialize
    @generator = EratosthenesGenerator.new
    extend OldCompatibility
    warn "Prime::new is obsolete. use Prime::instance or class methods of Prime."
  end
 
  class << self
    extend Forwardable
    include Enumerable
    # Returns the default instance of Prime.
    def instance; @the_instance end
 
    def method_added(method) # :nodoc:
      (class<< self;self;end).def_delegator :instance, method
    end
  end
  
  module OldCompatibility
  end
end

シングルトンパターン

さらに抜粋していくよ。

class Prime
  @the_instance = Prime.new
  
  class << self
    # Returns the default instance of Prime.
    def instance; @the_instance end
  end
end

Singleton モジュールは使わずにシングルトンパターンを実現しているよ。どうしてだろう。

旧 Prime 互換クラスのインスタンス

class Prime
  @the_instance = Prime.new

  # obsolete. Use +Prime+::+instance+ or class methods of +Prime+.
  def initialize
    @generator = EratosthenesGenerator.new
    extend OldCompatibility
    warn "Prime::new is obsolete. use Prime::instance or class methods of Prime."
  end
  
  module OldCompatibility
  end
end

OldCompatibility モジュールには旧 Prime 互換のインスタンスメソッドが宣言されているよ(中略しているよ)。 これを extend することによって、Prime.new で生成したインスタンスに特異メソッドを定義しているんだね。 互換が不要になったら OldCompatibility モジュールを削除するだけで済むからつよいよ。

Singleton モジュールを include すると Prime.new がプライベートクラスメソッドになってしまうから、旧 Prime 互換クラスのインスタンスを生成するためには Singleton モジュール を使わずにシングルトンパターンを実現する必要があったんだね。

ちなみに、デフォルトインスタンスを生成した時点では Prime.initialize は定義されていないから、このとき生成したインスタンスには OldCompatibility モジュールは extend されないよ。すごいね。

デフォルトインスタンスのメソッドをクラスメソッドとして使用する

class Prime
  class << self
    extend Forwardable
    
    def method_added(method) # :nodoc:
      (class<< self;self;end).def_delegator :instance, method
    end
  end
end

Fowardable モジュールを extend しているね。 Fowardable モジュールはクラスに対してメソッドの委譲機能を定義するモジュール。 この場合は、method_added の引数 method を instance に委譲するよ。

(class<<self;self;end) の戻り値は Prime の特異クラス。 ここでの self は Prime だから、Prime のクラスメソッドを定義するためには (class<<self;self;end) をレシーバにする必要があるんだね(class << self の中にまた class << self が出てくるからわかりづらいよね)。

Module#method_added はクラスやモジュールにインスタンスメソッドが定義されたときに呼び出されるメソッドだよ。 Prime にインスタンスメソッドが追加されると method_added が呼ばれて同名のクラスメソッドを定義、処理をデフォルトインスタンスに委譲することによって、デフォルトインスタンスのメソッドをクラスメソッドとして使用することができるんだね。

まとめ

よくわからない文体で書くのやめよう。