Ruby の メソッドについての解釈 (中級者向け)
本ブログはプログラミング中級者向けのトピックを扱う。
今回はRuby のメソッドについて。インスタンスメソッドやクラスメソッドなどがあるが、プログラミング始めの段階では、基本的にクラスからインスタンス化したのから呼べるのがインスタンスメソッド、クラスから直接呼べるのがクラスメソッド、みたいな区切りで理解している方が多いかと思う。以下のような感じに。
a = A.new a.instance_method A.class_method
今回は、クラスメソッドの詳細とインスタンスメソッドの関係について深掘りしてみよう。
メソッドの探索
さて、先ほどのサンプルで、a.instance_methodとしたが、このinstance_methodメソッドはRubyはどのように探してきているのか。例えばこんなコードの時。
class A
def instance_method
puts "from a."
end
end
module B
def instance_method
puts "from b."
end
end
class C < A
include B
def instance_method
puts "from c."
end
end
c = C.new
c.instance_method # どうなる?
これを見た時、パッと from cと答えられたら正解だ。まずは自分のクラスのメソッドから探索が始まる。もしC内になかった場合は次にモジュールBの中を探しに行く。そこにもなかったら親クラスのAを探しに行く。
これは直感的にわかることだろう。次に行く。
特異クラス
まずはシンプルな以下のコードを見ていただきたい。
class A
def a
puts "a called."
end
def self.b
puts "b called."
end
end
obj = A.new
def obj.c
puts "c called."
end
obj.a
A.b
obj.c
a called. b called. c called.
意図した結果になったことだろう。 aとbについては特筆すべき点はない。cについて見ていただきたい。RubyはCのように、 作ったオブジェクトからしか呼べないメソッドを定義する ことができる。これが特異メソッドと呼ばれる。他のAのインスタンス変数からこのcを呼ぶことはできない。
ここで疑問が起こる。 先ほどの メソッドの探索 で学んだメソッド探索において、このcメソッドはどこから見つけ出しているのか? 自分のクラスにもいないし、モジュールにもいないし、親クラスでもない。だけれども cメソッドを呼ぶことがでいる。
これが隠れている 特異クラス というものの存在だ。obj インスタンスに特異クラスというそのオブジェクトにしかない隠れたクラスを作り、そこにcメソッドを定義しているのである。これがメソッドの探索の最優先で呼ばれる。
クラスメソッドの正体
Ruby初心者の方は、クラスメソッドは A.class_method というような形でクラスから直接メソッドを呼べるもの、という認識だと思う。ただこれもRuby的には オブジェクトからメソッドを呼び出している 、というに過ぎない。ここがRubyにおいて重要な概念なんだけど、 クラスもオブジェクトである ということが言えるのだ。これをぱっとわかる人はまずいないと思う。ということでサンプル。
class A end puts A.class p A.methods a = A.new puts A.new.class p a.methods
Class [:allocate, :new, :superclass, :freeze.....] A [:nil?, :===, :=~, :!~, :eql?, :hash, :<=>, :class, .....]
ということだ。確かにAもオブジェクトっぽく振舞っていることがわかるだろう。AクラスはClassクラスのオブジェクトであることがわかる。
そしてここからが今回の感動ポイント。 Ruby の美しさを知る場面だ。
先ほどのクラスメソッドと特異メソッドの定義をまた挙げる。
class A
def self.b # def A.b とも書ける
puts "b called."
end
end
obj = A.new
def obj.c
puts "c called."
end
def obj.c はobjに対しての特異クラスであった。ということは、 この def self.b というのは、 Aクラスに対しての特異メソッドになるのだ!だからこそ、A.bと呼ぶことができるのである。
今まで何も考えずに、 def a と def self.b のように使い分けていたRubyエンジニアにはこのような違いがあることを知っていただきたい。
Ruby の特異クラス関連のイディオム
さてこれがわかえれば、Ruby のオープンソースによく出てくる以下のような表現も理解することができるようになる。
class << B はBの特異クラスを開いた状態にしてくれる。
obj = A.new class << obj def c puts "c called." end end def obj.c puts "c called." end
この2つの書き方は全く同じだ。特異クラス内で、cを定義するのが def cの書き方である。もう一つサンプル。
class A
def self.a
puts "a called."
end
class << A
def a
puts "a called."
end
end
end
これも同様で、self.aと同じ書き方になる。
Object#extend も理解できる。 a.extend(b) は aの特異クラスとしてbを追加するメソッド だ。
- インスタンスオブジェクトの場合
module D
def d
puts "from d."
end
end
class A
end
a = A.new
a.extend(D)
a.d # => "from d."
- クラスオブジェクトの場合
module B
def b
puts "from b."
end
end
class A
A.extend B # extend B と書いてもok.
end
A.b #=> "from b."
このことからもわかるように、 インスタンスメソッドもクラスメソッドも結局はオブジェクトから呼び出すメソッド 。そんな認識を持っていただけたら今回はいいのではないだろうか。
Ruby の美しさを感じることができたら何よりである。