本ブログはプログラミング中級者向けのトピックを扱う。
今回は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 の美しさを感じることができたら何よりである。