Subscribed unsubscribe Subscribe Subscribe

Rubyのメソッド周りの動作まとめ

今までRubyにあまり触れていなかったため、 初めてのRubyメタプログラミングRuby 第2版Rubyのしくみ -Ruby Under a Microscope- を一気に読んだ。

この記事の内容は、学習の記録&復習としてメソッド周りの動作をまとめたもの。

公式ドキュメントは読んだがそれ以上はやってないという方にはもしかしたら役に立つかもしれない。 もうすでにバリバリ使ってる方には当たり前の内容かもしれない。

コードの動作は

$ ruby -v
ruby 2.3.1p112 (2016-04-26) [x86_64-linux-gnu]

で確認した。

目次

  • メソッドの実行
  • メソッドの探索
  • メソッドの定義

メソッドの実行

メソッドの呼び出し時の動作:

  • 常にレシーバが設定される
  • レシーバを明示しない場合は呼び出したスコープでのselfがレシーバとなる
  • メソッド内のselfは呼び出されたときのレシーバとなる
def whoami(); self; end

whoami # => main //トップレベルのselfはmainオブジェクト(Objectクラスのインスタンス)
class C
  whoami # => C
  def f
    whoami
  end
end
o = C.new
o   # => #<C:0x000000008c7c88>
o.f # => #<C:0x000000008c7c88> // レシーバはo. whoami内のselfはo

sendを使えばメソッド名のシンボルを使って呼び出すこともできる。 その際のレシーバはsendのレシーバが使われる。

# つづき
o.send(:f) # => #<C:0x000000008c7c88> // レシーバはo. whoami内のselfはo

メソッドの探索

オブジェクトは内部で

  • そのクラスへの参照をもつ
  • スーパークラスへの参照をもつ(クラスオブジェクトの場合)

これらの参照先はclassメソッドやsuperclassメソッドで得られるものとは異なる。 説明のため以下ではそれらの参照を単にそれぞれklass, superという言葉で表す。 (klass、superはRubyC言語での実装で使われているポインタの名前。 もしかしたら新しいバージョンでは実装や名前が変わっているかもしれないが、大きく実装が変わらない限り当分このイメージで問題ないだろう)

obj.method_name()としてメソッドを実行すると、

  1. objのklassが指すクラスにmethod_nameが定義されているか
  2. そのsuperのクラスに定義されているか
  3. そのsuperのクラスに定義されているか

と探索し、最初に見つかったものを実行する。

ancestorsでそのsuperのチェーンを確認できる。

class P; end
module M; end

module N
    include M # N -super-> M
end
N.ancestors # => [N, M]

class C < P # C -super-> P
  include N # C -super-> N -super-> M -super-> P
end
C.ancestors # => [C, N, M, P, Object, Kernel, BasicObject]

N.ancestors # => [N, M] // インクルード時にはmoduleのコピーをインクルードするのでここで M -super-> Pになっているわけではない

メソッドを探索し最後まで見つからなければ、最初に戻ってmethod_missingメソッドを探索する。 BasicObjectクラスにはもともとmethod_missingが定義されているため何もしなければそれが実行される。

BasicObject.private_instance_methods(false) # => [..., :method_missing, ...]

method_missingを定義すれば存在しないメソッドを処理することもできる。(ゴーストメソッドとよばれる)

class C
    def method_missing(name)
        "#{name} is called!"
    end
end
C.new.hogehoge # => "hogehoge is called!"
C.new.helloworld # => "helloworld is called!"

メソッドの定義

defによってメソッドを定義する方法は2つ

  1. def + メソッド名で定義( def methodname() ...)
  2. def + オブジェクト名.メソッド名 で定義( def obj.methodname() ...)

1. def methodname() ...で定義

この場合、defが現れた場所のカレントクラスインスタンスメソッドとして定義される。 ("カレントクラス"はメタプログラミングRuby 第2版で使われていた用語で、公式の用語ではなさそう)

カレントクラスはスコープが参照しているクラスのことで、次のようになっている。

# カレントクラス: Object
module M
  # カレントクラス: M
  class C
    # カレントクラス: C. hogeはCのインスタンスメソッドとなる
    def hoge()
      # カレントクラス: self.class
    end
  end
  # カレントクラス: M
end
p M::C.instance_methods(false) # => [:hoge]

2. def obj.methodname() ...で定義

この場合、そのobjの固有のクラス(特異クラス)のインスタンスメソッドに定義される。 (以下、objオブジェクトの特異クラスには#を付けて#objと表す)

class C
  def func
  end
end
obj = C.new
obj2 = C.new
def obj.sfunc #  objの特異クラス#objのインスタンスメソッドとして定義
end
obj.sfunc
obj2.sfunc # Error

p obj.class.instance_methods(false) #=> [:func]
p obj.singleton_class.instance_methods(false) #=> [:sfunc]

上記のobj.sfuncobjの特異メソッド(singleton method)と呼ばれる。 このときメソッドの探索で説明したklassとsuperは

obj -klass-> #obj -super-> C -super-> ...

のようになっており、#objにsfunc、Cにfuncが定義されているため、前述のメソッドの探索でobj.funcobj.sfuncも実行できる。 obj2.sfuncがErrorなのはその探索するクラスにsfuncが定義されていないため。

クラスでは…

クラスもオブジェクトであるため、

def C.classfunc
end

とすればクラスの特異メソッド(クラスメソッド)が定義でき、C.classfuncで実行できる。 class ClassName ... end内のselfはそのクラスになることから、上記は以下のようにも定義できる。

class C
  def self.classfunc # ここでのselfはC. Cの特異クラスに定義
  end
end

class << objectとするとそのオブジェクトの特異クラスをカレントクラスとしてオープンできるので、下のようにもかける。

class << C
  # このスコープでのカレントクラスはCの特異クラス
  def classfunc
  end
end

クラスの特異クラスのことをメタクラスとよぶこともある。

スーパークラスの特異クラスは特異クラスのスーパークラスなので、クラスメソッドはサブクラスでも実行できる。

class P
    def self.classf
        "ok"
    end
    classf # => "ok"
end

class C < P
    classf # => "ok" // selfはC. C -klass-> #C -super-> #P
end

C.singleton_class.superclass == P.singleton_class # => true
 P --klass-> #P (def classf)
 ^           ^
super       super    
 ^           ^
 C --klass-> #C

クラスがオブジェクトであるように、特異クラスもオブジェクトであるため特異クラスの特異クラスも作成できる。(使い道があるかどうかは別として)

トップレベ

トップレベルでのカレントクラスはObjectであり、 トップレベルでオブジェクト指定なしでメソッドを定義するとObjectのprivateメソッドとして定義される。 この事実と、

  • レシーバを指定せずにメソッドを実行するとselfがレシーバとして設定される
  • Objectはあらゆるクラスのスーパークラス

ということから、トップレベルで定義した関数はあらゆる場所でレシーバなし実行できる。 (Objectのサブクラスではないクラスも作れるため、全ての場所で実行できるわけではない。)

def top; "ok!" end
Object.private_instance_methods(false) # => [..., :top, ...]

class C
    top() # => ok! // selfはC。 CはClassクラスのインスタンス
    def hoge
        top()
    end
end
C.new.hoge # => ok! // hoge内のselfはCクラスのインスタンス

class Clean < BasicObject
    top() # => ok! // selfはClean, CleanはClassクラスのインスタンス
    def fuga
        top()
    end
end
Clean.ancestors # => [Clean, BasicObject]
Clean.new.fuga # NoMethodError // topがundefined. selfのメソッド探索ルートにObjectがいないため

ちなみにprivateメソッドはレシーバを明示できないため(privateの制約)、 トップレベルのメソッドはレシーバなし実行できるというよりは、 レシーバを明示したobj.method()の形式では実行できない。


特異クラスはいつ作られるのか(おまけ)

p ObjectSpace.count_objects[:T_CLASS] # => 612
class C; end
p ObjectSpace.count_objects[:T_CLASS] # => 614 // Cと#Cが作られた?
o = C.new
p ObjectSpace.count_objects[:T_CLASS] # => 614
def o.f; end
p ObjectSpace.count_objects[:T_CLASS] # => 615 // #oがつくられた?

クラスの数だけで判断すると、クラスの特異クラスはクラス定義時に作成される、 クラスでないオブジェクトの特異クラスは必要になったら作成される、っぽい。 (前半の、クラス定義時に2つクラスができる、というのはRubyのしくみ -Ruby Under a Microscope-に記載されている内容。)

初めてのRuby

初めてのRuby

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

Rubyのしくみ -Ruby Under a Microscope-

Rubyのしくみ -Ruby Under a Microscope-