LoginSignup
158
150

More than 5 years have passed since last update.

Ruby基礎 | requireとincludeとextendの違いを確認(includeとextendはメソッド探索で確認)

Last updated at Posted at 2017-09-07

まだ腑に落ちていない中、こちらみてドキッとしたので、整理始めました。

Rubyにおけるincludeとrequireの区別

さっきMatz氏が「Rubyにおけるincludeとrequireの区別に苦労するような人には、そりゃRubyは向いてないと思う。」ということをつぶやいていた

ちょっとはしっくりきた、require, include/prepend, extendの調査結果です。(内容は諸先輩方が書いたわかりやすい記事の写経です。つまづいたところは自分なりに整理してまとめました。)

requireについて

参考

うまく探せず、なぜか英語のサイトへ。。。
Ruby Methods: differences between load, require, include and extend in Ruby.

It reads the file from the file system, parses it, saves to the memory and runs it in a given place. What does it mean? Simply, even if you change the required file when the script is running, those changes won't be applied - Ruby will use the file from memory, not from the file system.

How do I import ruby classes into the main file?

In order to call any class, or module from another file, you have to require it.

要は、requireは、ファイルをメモリに読み込むということですかね。

requireはファイルの読み込み

いつもクラス定義とmain文が一緒だったので理解できていませんでした。
classとmain分別ファイルに分けて書くと、requireメソッドの意味がよくわかります。

確認

classとmain文がトゥゲザー → requireメソッド無しで動く

together.rb
class Neko
  class << self
    def hello
      p 'hello'
    end
  end
  def bye
    p 'bye'
  end
end

Neko.hello #=> hello

neko = Neko.new()
neko.bye #=> bye

classとmain文がトゥゲザーしてない、かつrequireメソッド無 → 動かない

seperate_class.rb
class Neko
  class << self
    def hello
      p 'hello'
    end
  end
  def bye
    p 'bye'
  end
end
seperate_main.rb
# あとで追加する
# あとで追加する

Neko.hello

neko.bye
neko = Neko.new()

実行すると動かない。

 $ ruby seperate_main.rb
seperate_main.rb:4:in `<main>': uninitialized constant Neko (NameError)

classとmain文がトゥゲザーしてない、requireメソッド有 → 動く

ここでrequire文を追加すると、動く、と。

seperate_main.rb
# あとで追加する
require './seperate_class' # 追加
# あとで追加する
            :

実行する。

$ ruby seperate_main.rb
"hello"
"bye"

まとめ

main文が分かれていて、別のファイルのクラスを読み込みたければ、requireが必要になる、ってわけなのですね。
だから、CSVファイルを作成するときやスクレイピングするときにはこう書くのか。

CSVファイルを操作する時
require 'csv'
Nokogiri使うとき
# URLにアクセスするためのライブラリの読み込み
require 'open-uri'
# Nokogiriライブラリの読み込み
require 'nokogiri'

load との違いは、requireはすでにメモリに展開されていた場合はたとえ対象ファイルに変更があっても読み込みしないが、loadはする、そうです。
読み込み先が頻繁に更新されるときは、loadを使ってその変更をタイムリーにひろう、ために使うそうです。

So when to use load? In most cases you’ll use require, but there are some cases when load is useful, for example, when your module changes frequently you may want to pick up those changes in classes that loads this module.

includeについて

次はincludeいきます。各メソッドを理解するにはメソッド探索の理解が助けてくれそう、ということで、やってみました。

参考

【ruby】 メソッド探索から見る、モジュール・特異メソッド・特異クラス

includeはModuleを使って、メソッドを追加する

先にまとめです。

  • includeはModuleを呼ぶメソッド。
  • メソッド探索を追うと、どのクラスのメソッドが最初に呼ばれるかがわかる。

素振りしながら、確認していきたいと思います。ステップはこちらです。

  1. インスタンスメソッド参照
  2. 存在しないメソッド参照
  3. 継承
  4. 継承+オーバライド
  5. モジュール(include編)
  6. 複数モジュール(include編)
  7. モジュール(prepend編)
  8. 複数モジュール(prepend編)
  9. 特異メソッド
  10. クラスメソッド その1(アクシデントあり)
  11. クラスメソッド その2(アクシデント解決)

一通り試してみました。

確認

インスタンスメソッド参照

ファイルの準備:

neko.rb
class Neko
  class << self
    def class_method
      "class method in Neko"
    end
  end

  def instance_method
    "instance method in Neko"
  end
end
puts_safe.rb
def puts_safe
  puts yield
rescue => e
  puts "Error :< - #{e}"
end
msearch1.rb
# インスタンスからメソッドを呼ぶ。
require './neko'
require './puts_safe'

puts "<<class method>>"
puts_safe { Neko.class_method } #=> class method in Neko

puts "<<instance method of neko1>>"
neko1 = Neko.new
puts_safe { neko1.instance_method;  } #=> instance method in Neko

puts "<<instance method of neko2>>"
neko2 = Neko.new
puts_safe { neko2.instance_method } #=> instance method in Neko

実行:

$ ruby msearch1.rb
<<class method>>
class method in Neko
<<instance method of neko1>>
instance method in Neko
<<instance method of neko2>>
instance method in Neko

いつものインスタンスメソッドですが、実はインスタンスにできるのではなくクラスを参照しているとのこと。ここはOKかな。図も写経していきます。

図:
ruby_tokui_class.jpg

存在しないメソッド参照

これをすると、メソッド探索されていることがわかります。

ancestorsでスーパクラスを確認してみる。
表示されたクラスらに向けて、メソッド探索が行われている。メソッド探索は上にどんどんよじ登っていく感じですね。

2.3.0 :002 > load './neko.rb'
 => true
2.3.0 :003 > Neko.ancestors
 => [Neko, Object, Kernel, BasicObject]
2.3.0 :004 >

ファイル準備:

msearch2.rb
# 未定義定義のメソッドを呼ぶ。
require './neko'
require './puts_safe'

puts "<<missing method of neko2>>"

neko3 = Neko.new
puts_safe { neko3.missing_method } #=> Error :< - undefined method `missing_method' for #<Neko:0x0000000179ea18>
#It is a memory reference(unique storage location in memory) of object you created.
#Ref: "https://stackoverflow.com/questions/19609880/what-does-the-0x-in-ruby-objects-inspect-mean"

結局、missing_methodが見つからなかったためundefined method のエラーが出ていたということ。

図:
ruby_tokui_class (1).jpg

継承

これをすると、メソッド探索するためによじ登っている感がわかります。

Leopardクラスのファイル:

leopard.rb
class Leopard < Neko

end

実行ファイル:

msearch3.1.rb
# クラスを継承する。
require './neko'
require './puts_safe'
require './leopard'

puts "<<leo1>>"

leo1 = Leopard.new
puts_safe { leo1.instance_method } #=> instance method in Neko

メソッドがNekoクラスで見つかります。まだOKかな。

図:

ruby_tokui_class.png

継承+オーバライド

これをすると、メソッド探索のよじ登り方に変化が出てくるのがわかります。
オーバライドしてみて、メソッド探索が見てみたくなるところです。

Jaguarクラスのファイル:

jaguar.rb
class Jaguar < Neko
  def instance_method
    puts "instance method in Jaguar"
  end
end

実行ファイル

msearch3.2.rb
# クラスを継承する。オーバライドもする。
require './neko'
require './puts_safe'
require './jaguar'

puts "<<jag1>>"

jag1 = Jaguar.new
puts_safe { jag1.instance_method } #=> instance method in Jaguar

メソッドがJaguarクラスで見つかります。Nekoクラスによじ登る前に見つかるからなのですね。

図:

ruby_tokui_class (1).png

モジュール(include編)

これを試すと、モジュールの組み込みがどうなされているかがわかります。
図の中のモジュールの入り方が要チェックです。

Tigerクラスのファイル

tiger.rb
require './fur'

class Tiger < Neko
  include Fur
  #prepend Fur
  def instance_method
    puts "instance method in Tiger"
  end
end

Furモジュールのファイル(毛皮をイメージ)

fur.rb
module Fur
  def m_method_for_color(color = "white")
    "module method: my fur is #{color} color."
  end

  def m_method_for_length(length = 2)
    "module method: my fur is #{length} cm."
  end

end

実行ファイル

msearch5.1.rb
# モジュールをincludeする。
require './neko'
require './puts_safe'
require './tiger'

puts "<<tig1>>"

tig1 = Tiger.new
puts_safe { tig1.m_method_for_color } #=> module method: my fur is white color.

モジュールをincludeすると、"クラス-->モジュール-->親クラス"と、ちょうど真ん中に新しい箱が入ってきます。ですので、メソッド探索の経路に新しいメソッドが入ってくるわけです。

メソッドを増やしている感がなんとなくわかりました。

あと、実はKernelクラスはincludeとされたモジュールであるとのことで、同様に"クラス-->スーパークラス"の経路の中に書いてみました。

図:
ruby_tokui_class (2).png

複数モジュール(include編)

同様にこれを試すと、モジュールの組み込みがどうなされているかがわかります。

Pumaクラスのファイル

puma.rb
require './fur'
require './claw'

class Puma < Neko
  include Fur
  include Claw
#  prepend Fur
#  prepend Claw
  def a_instance_method
    puts "a instance method in Puma"
  end
end

Clawクラスのファイル(するどい爪をイメージ)

# Furモジュールのメソッドのうち一つをオーバーライドしてみる
module Claw #つめ
  def m_method_for_color(color = "white")
    "module method: my claw is #{color} color."
  end
end

実行ファイル

msearch5.2.rb
# モジュールを2個includeする。
require './neko'
require './puts_safe'
require './puma'

puts "<<pum1>>"

pum1 = Puma.new
puts_safe { pum1.m_method_for_color } #=>module method: my claw is white color.
puts_safe { pum1.m_method_for_length } #=>module method: my fur is 2 cm.

複数モジュールをincludeしたことで、2つの箱が経路に入ってきます。

すると、m_method_for_colorm_method_for_length も、Furモジュールのメソッドなのですが、Clawモジュールが割り込んできたことでm_method_for_colorがオーバーライドされていることになります。

図:
ruby_tokui_class (5).jpg

ここにきてやっと、モジュールとincludeを使って、メソッドを増やしている感がなんとなくわかりました。

モジュール(prepend編)

includeのイメージがつくと、こちらもすんなり理解できました。
Tigerクラスのinclude部分をprependに変更して実行します。

Tigerクラスのファイル(修正)

tiger.rb
require './fur'

class Tiger < Neko
  #include Fur
  prepend Fur               # prependを使う
  def instance_method
    puts "instance method in Tiger"
  end
end

実行ファイルは、msearch5.1.rbをそのままです。
"クラス-->スーパークラス"の前に箱が入り込んでくるだけですね。

図:
ruby_tokui_class (3).png

だんだんすんなり理解できるようになってきたかな。

複数モジュール(prepend編)

includeのイメージがつくと、こちらもやっぱりすんなり理解できました。
Pumaクラスのinclude部分をprependに変更して実行します。

Pumaクラスのファイル(修正)

puma.rb
require './fur'
require './claw'

class Puma < Neko
#   include Fur
#   include Claw
   prepend Fur
   prepend Claw
  def a_instance_method
    puts "a instance method in Puma"
  end
end

実行ファイルは、msearch5.2.rbをそのままです。箱が2個になっただけですね。

図:
ruby_tokui_class (7).jpg

注意点はprependの表記の順番ですかね。

特異メソッド

「メソッドはクラスに属している」のならば、特異メソッドはどのクラスに属しているのか、疑問ですよね。
「オブジェクトの参照しているクラスに追加される」のであれば、他のオブジェクトからも呼び出せてしまうのでこれは違います。

たしかに!
こちらもメソッド探索をもとに考えるとすっきり考えられました。

実行ファイル:

msearch6.rb
# 特異メソッドを特定のオブジェクトに追加
require './neko'
require './puts_safe'

puts "<<neko4>>"
neko4 = Neko.new

# 特異メソッドの追加
def neko4.tokui_method
  "tokui_method"
end

puts_safe { neko4.tokui_method  } #=> tokui method

puts "<<neko5>>"
neko5 = Neko.new
puts_safe { neko5.tokui_method } #=>undefined method `tokui_method' for #<Neko:0x00000001cf1dd0>

図:
ruby_tokui_class (8).jpg

オブジェクトは必ず一つの特異クラスを持っている

上図の#neko4が特異クラスです。こういうクラスが存在するのを知らなかった。

クラスメソッド その1(アクシデントあり)

ラストです。ちょっとアクシデントがありました。

実行ファイル:

msearch7.1.rb
require './neko'
require './puts_safe'

neko6 = Neko.new

puts_safe { neko6.instance_method }  # => (1) instance method in Neko
puts_safe { neko6.class_method }     # => (2) undefined method `class_method' for #<Neko:0x00000000b5e0f0>
puts_safe { Neko.instance_method }   # => (3) wrong number of arguments (given 0, expected 1)
puts_safe { Neko.class_method }      # => (4) class method in Neko

図:
ruby_tokui_class (9).jpg

(1), (2), (4)は自分も期待通りの結果が出てよかったのですが、(3)がなぜundefined methodが表示されず困りました。
でも上でメソッド探索の"メ"ぐらいは理解できていたので、”もしかしてinstance_method”ってデフォルトで定義されている?とあたりを付けることができました。

クラスメソッド その2(アクシデント解決)

別途Inuクラスを作って、メソッド名に"inu_"を付けて確認してみました。

Inuクラス(Nekoクラスとほとんど一緒)

inu.rb
class Inu
  class << self
    def inu_class_method # メソッド名をユニークにしました。
      "class method in Inu"
    end
  end

  def inu_instance_method # メソッド名をユニークにしました。
    "instance method in Inu"
  end
end

実行ファイル:

msearch7.2.rb
require './inu'
require './puts_safe'

inu1 = Inu.new

puts_safe { inu1.inu_instance_method }  # => (1) instance method in Inu
puts_safe { inu1.inu_class_method }     # => (2) undefined method `class_method' for #<Neko:0x00000000b5e0f0>
puts_safe { Inu.inu_instance_method }   # => (3) undefined method `inu_instance_method' for Inu:Class
                                        # =>     Did you mean?  instance_method    # いました!!
                                        # =>                    public_instance_method
                                        # =>                    inu_class_method
                                        # =>                    instance_methods

puts_safe { Inu.inu_class_method }      # => (4) class method in Inu

併せて、instance_methodがどこにいるか見てみます。

確認結果:

2.3.0 :142 > Inu.singleton_class.superclass.superclass.superclass.superclass.instance_methods(false).grep(/instance_method/)
 => [:public_instance_methods, :instance_methods, :private_instance_methods, :protected_instance_methods, :instance_method, :public_instance_method]
# instance_methodってすでにあるのですね。。。無知でした。。
# ちなみにこのクラスが何かを見ると↓
2.3.0 :143 > Inu.singleton_class.superclass.superclass.superclass.superclass
  => Module
# Moduleクラスでした。

これによってメソッド探索の最終の図も確認することができました。

図:
ruby_tokui_class (10).jpg

まとめ

メソッド探索のまとめ

「レシーバから右へ一歩、そこからは上へ」
と表現しています。

この表現に尽きます。図を見ながらだとよくわかりました。

オブジェクトがレシーバでも「レシーバから右へ一歩、そこからは上へ」
クラスがレシーバでも「レシーバから右へ一歩、そこからは上へ」

ところで特異クラスは、どのようにしてプロの方々は使うのか?

ちょっとここまで調べるほど体力が残っていませんでした。
なにかメリットはあると思うので、デザインパターンなど次に調べてみたいと思います。

extendについて

requireとincludeを調べていたら、extendもあったことに気づき、併せてまとめてみました。

参考

【Ruby】includeとprependとextendの違いと用途

メソッド探索の例を理解すると、こちらも理解しやすそうです。

extendは、レシーバの特異クラスにModuleのメソッドを組み込む。

"extend = レシーバの特異クラスに対してModuleをincludeする"という感じですね。

確認

これをすると、extendでモジュールがどこに組み込まれているかがわかります。

今度は、Nekoクラスを継承したPantherクラスを使います。そこにTailモジュール(しっぽをイメージ)をextendして確認します。

Pantherクラスのファイル:

panther.rb
require './tail'

class Panther < Neko
  extend Tail
  def pan_instance_method
    puts "instance method in Panther"
  end
end

Tailモジュールのファイル:

tail.rb
module Tail
  def m_method_for_color(color = "white")
    "module method: my tail is #{color} color."
  end

  def m_method_for_length(length = 25)
    "module method: my tail is #{length} cm."
  end

end

実行結果:

2.3.0 :001 > load './tail.rb'
 => true
2.3.0 :002 > load './neko.rb'
 => true
2.3.0 :003 > load './panther.rb'
 => true
2.3.0 :004 > Panther.ancestors
 => [Panther, Neko, Object, Kernel, BasicObject]  # ここにはTailモジュールはいない。
2.3.0 :005 > Panther.singleton_class.ancestors #こっちでした!
 => [#<Class:Panther>, Tail, #<Class:Neko>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]

図:
ruby_tokui_class (6).png

まとめ

走り書きですが、includeとextendの違いをまとめておきます。

# メソッド 定義 まとめ
1 Module#include クラス定義の直下でincludeする レシーバにメソッドを組み込む→レシーバはincludeの定義が存在するクラスのこと。該当のクラスにModuleのメソッドを組み込む。
2 Module#include クラス定義のclass << selfの中でincludeする レシーバにメソッドを組み込む→レシーバはclass << selfなので定義したクラスの特異クラスのこと*。該当の特異クラスにModuleのメソッドを組み込む。つまりクラスメソッドか!
3 Object#extend クラス定義の直下でextendする レシーバの特異クラスにメソッドを組み込む→特異クラスにメソッドが入るので、クラスメソッド扱い。

*下の番外編を読んで、なんとなく"class << self"が特異クラスを指しているなと予想しただけです。完全に理解できていません。

番外編(なぜ class << self と書くのか?)

参考

Ruby 初級者のための class << self の話 (または特異クラスとメタクラス)

なぜこう書けるのでしょうか。

singleton1.rb
class Neko
  class << self   # ここ!!わかるようなわからないような。
    def hello
      p 'hello'
    end
  end
end

Neko.hello         #=> hello

neko = Neko.new()
neko.hello         #=> singleton1.rb:12:in `<main>': undefined method `hello' for #<Foo:0x00000002178340> (NoMethodError)

クラスメソッドの定義の仕方は2つ

  1. def self.method_name (特異メソッド形式)
  2. class << self (特異クラス形式)

Ruby では#1はオブジェクトに対して直接メソッドを定義することができます。プロの方々が商用でどのように使うのかは想像もつきません。
ひとまずdef neko.sayと定義することだけ今は覚えておきます。

# 特異メソッド
neko = 'neko'
inu = 'inu'

def neko.say # ここの書き方だけチェック。
  print self
end

neko.say #=> nekoneko
inu.say #=> nekotokui_method.rb:10:in `<main>': undefined method `say' for "inu":String (NoMethodError)

#2の話を進めます。

なぜ class << self と書けるのか。

同サイトの順番に沿ってコードを読むとよくわかりました。

1) いつも通りのクラスを書く

クラス定義のシンタクス class を使って Bar クラスを定義する場合。

singleton2.rb
class Neko # いつもの書き方ですかね。
  def hello
    p 'hello'
  end
end
neko = Neko.new
neko.hello #=> hello

2) 'class'の書き方を'Neko = Class.new'に変更する

Class クラスのインスタンスを生成して、定数 Bar へ束縛する場合。

この場合はNekoへ束縛しています。

singleton3.rb
Neko = Class.new do # ここの書き方が違う!
  def hello
    p 'hello'
  end
end
neko = Neko.new
neko.hello #=> hello

3) 特異メソッド形式でNekoにメソッドを増やします

定数 Bar に入っている Class クラスのオブジェクトへ特異メソッドを定義してみましょう。

singleton4.rb
Neko = Class.new do
  def hello
    p 'hello'
  end
end

def Neko.bye     # ここで一番最初に出てきた特異メソッド形式でメソッドを追加しているのね。
  p 'bye bye'
end

neko = Neko.new
neko.hello #=> hello

Neko.bye #=> bye bye

4) 特異メソッド形式 → "<<" 形式に変更する

オブジェクトの特異クラスの引き出しの記法 << をあわせて考えると、最初のイディオムがやろうとしていることがわかってきます。

singleton5.rb
Neko = Class.new do
  def hello
    p 'hello'
  end
end
class << Neko  # 特異クラスの記述でクラスメソッドを記述。
  def bye
    p 'bye bye'
  end
end
neko = Neko.new
neko.hello #=> hello

Neko.bye #=> good bye

ここで最後。selfが登場する理由がわかる。

6) "<<" 部分をクラス定義の中に放り込む

せっかくなので、クラス定義をすべて class 記法での宣言の中に入れてみましょう。

singleton6.rb
class Neko   # クラスの定義はこういうシンタックスシュガーだったんだ。
  def hello
    p 'hello'
  end

  class << self   # クラス定義の中に来ました。クラス定義の中で
      def bye     # Nekoクラスに入ってるインスタンスを取得するには self を使う。
      p 'bye bye'
    end
  end
end
neko = Neko.new
neko.hello #=> hello

Neko.bye #=> bye bye

おー、つながりましたー。

謝辞

参考にさせていただいたサイトは大変勉強になりました。本当に感謝です。

158
150
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
158
150