basyura's blog

あしたになったらほんきだす。

Ruby7不思議 - 特異クラス・特異メソッド・クラスメソッド

整理中。
「特異」って言葉に慣れてないので想像しにくいのだけど、

1 特別に他とちがっていること。また、そのさま。「彼はこの会社では―な存在だ」
2 特にすぐれていること。また、そのさま。「―な才能の持ち主」

特異

マニュアルにある言葉をひっぱってくると

  • 特異メソッドは特定のインスタンスに固有のメソッド
  • 特異クラスはある特定のオブジェクトだけのための仮想的なクラス
  • クラスメソッドはクラスの特異メソッド

モヤモヤ。

言葉の定義はモヤモヤするのだけど、 rails みたいに拡張しまくるプログラムを書くためには何がしたいかっていうと、

  • 動的に、インスタンスにメソッドを追加したい
  • 動的に、クラスにメソッドを追加したい
  • 動的に、クラスにクラスメソッドを追加したい

になる。動的に追加するには、特異クラスを使うことが多い(のだと思う)。

class << self
  # 本文
end

上記のように定義文の中で宣言するか

sig = class << self ; self end

特異クラスを取り出して操作する。

さらに、特異クラスを取り出す(定義する)際のコンテキストも大事。インスタンスなのか、モジュールなのか。

実際にやってみる

動的に、インスタンスにメソッドを追加する

class A ; end
a = A.new
a.instance_eval do |obj|
  class << self
    define_method :hello , Proc.new{ 'world' }
  end
end
a.hello     #=> world
A.new.hello #=> undefined method

インスタンスのコンテキストで特異クラスを取り出してメソッドを定義する。インスタンス固有のメソッド定義になる。
以下のようにも書ける。

class A ; end
a = A.new
a.instance_eval do |obj|
  sig = class << self ; self end
  sig.send :define_method , :hello , Proc.new{ 'world' }
end
puts a.hello     #=> world
puts A.new.hello #=> undefined method

動的に、クラスにメソッドを追加する

全てのインスタンスで使えるメソッドを定義する。

class A ; end
a = A.new
a.class.module_eval do |mod|
  define_method :hello , Proc.new{ 'world' }
end
puts a.hello     #=> world
puts A.new.hello #=> world

モジュールのコンテキストでメソッドを定義する。クラス共通のメソッド定義になる。

動的に、クラスにクラスメソッドを追加する

class A ; end
a = A.new
a.class.module_eval do |mod|
  class << self
    define_method :hello , Proc.new{ 'world' }
  end
end
puts A.hello #=> world

モジュールのコンテキストで特異クラスを取り出してメソッドを定義するクラスメソッド定義になる。
以下のようにも書ける。

class A ; end
a = A.new
a.class.module_eval do |mod|
  sig = class << self ; self end
  sig.send :define_method , :hello , Proc.new{ 'world' }
end
puts A.hello #=> world

クラスメソッドはインスタンスから定義するシチュエーションが思い浮かばない・・・。rails っぽく(?) table 名をクラス変数としてアクセスできるようにするのはこんな感じだろうか。

class Z
  def self.set_table_name(table)
    @table = table
  end
  module_eval do |mod|
    sig = class << self ; self end
    sig.send :define_method , :table_name , Proc.new{ @table }
  end
end
class A < Z
  set_table_name :table_a
end
class B < Z
  set_table_name :table_b
end
puts A.table_name #=> table_a
puts B.table_name #=> table_b

@table はクラスのインスタンス変数。でいいのかな。
またこんがらがってきた。

メタプログラミングRuby

メタプログラミングRuby

読み直すか。