読者です 読者をやめる 読者になる 読者になる

ボクココ

サービス開発を成功させるまでの歩み

Ruby の メソッドについての解釈 (中級者向け)

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.cobjに対しての特異クラスであった。ということは、 この def self.b というのは、 Aクラスに対しての特異メソッドになるのだ!だからこそ、A.bと呼ぶことができるのである。

今まで何も考えずに、 def adef 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 の美しさを感じることができたら何よりである。