まだ腑に落ちていない中、こちらみてドキッとしたので、整理始めました。
さっき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メソッド無しで動く
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メソッド無 → 動かない
class Neko
class << self
def hello
p 'hello'
end
end
def bye
p 'bye'
end
end
# あとで追加する
# あとで追加する
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文を追加すると、動く、と。
# あとで追加する
require './seperate_class' # 追加
# あとで追加する
:
実行する。
$ ruby seperate_main.rb
"hello"
"bye"
まとめ
main文が分かれていて、別のファイルのクラスを読み込みたければ、requireが必要になる、ってわけなのですね。
だから、CSVファイルを作成するときやスクレイピングするときにはこう書くのか。
require 'csv'
# 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を呼ぶメソッド。
- メソッド探索を追うと、どのクラスのメソッドが最初に呼ばれるかがわかる。
素振りしながら、確認していきたいと思います。ステップはこちらです。
- インスタンスメソッド参照
- 存在しないメソッド参照
- 継承
- 継承+オーバライド
- モジュール(include編)
- 複数モジュール(include編)
- モジュール(prepend編)
- 複数モジュール(prepend編)
- 特異メソッド
- クラスメソッド その1(アクシデントあり)
- クラスメソッド その2(アクシデント解決)
一通り試してみました。
確認
インスタンスメソッド参照
ファイルの準備:
class Neko
class << self
def class_method
"class method in Neko"
end
end
def instance_method
"instance method in Neko"
end
end
def puts_safe
puts yield
rescue => e
puts "Error :< - #{e}"
end
# インスタンスからメソッドを呼ぶ。
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かな。図も写経していきます。
存在しないメソッド参照
これをすると、メソッド探索されていることがわかります。
ancestorsでスーパクラスを確認してみる。
表示されたクラスらに向けて、メソッド探索が行われている。メソッド探索は上にどんどんよじ登っていく感じですね。
2.3.0 :002 > load './neko.rb'
=> true
2.3.0 :003 > Neko.ancestors
=> [Neko, Object, Kernel, BasicObject]
2.3.0 :004 >
ファイル準備:
# 未定義定義のメソッドを呼ぶ。
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 のエラーが出ていたということ。
継承
これをすると、メソッド探索するためによじ登っている感がわかります。
Leopardクラスのファイル:
class Leopard < Neko
end
実行ファイル:
# クラスを継承する。
require './neko'
require './puts_safe'
require './leopard'
puts "<<leo1>>"
leo1 = Leopard.new
puts_safe { leo1.instance_method } #=> instance method in Neko
メソッドがNekoクラスで見つかります。まだOKかな。
図:
継承+オーバライド
これをすると、メソッド探索のよじ登り方に変化が出てくるのがわかります。
オーバライドしてみて、メソッド探索が見てみたくなるところです。
Jaguarクラスのファイル:
class Jaguar < Neko
def instance_method
puts "instance method in Jaguar"
end
end
実行ファイル
# クラスを継承する。オーバライドもする。
require './neko'
require './puts_safe'
require './jaguar'
puts "<<jag1>>"
jag1 = Jaguar.new
puts_safe { jag1.instance_method } #=> instance method in Jaguar
メソッドがJaguarクラスで見つかります。Nekoクラスによじ登る前に見つかるからなのですね。
図:
モジュール(include編)
これを試すと、モジュールの組み込みがどうなされているかがわかります。
図の中のモジュールの入り方が要チェックです。
Tigerクラスのファイル
require './fur'
class Tiger < Neko
include Fur
#prepend Fur
def instance_method
puts "instance method in Tiger"
end
end
Furモジュールのファイル(毛皮をイメージ)
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
実行ファイル
# モジュールを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とされたモジュールであるとのことで、同様に"クラス-->スーパークラス"の経路の中に書いてみました。
複数モジュール(include編)
同様にこれを試すと、モジュールの組み込みがどうなされているかがわかります。
Pumaクラスのファイル
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
実行ファイル
# モジュールを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_color
もm_method_for_length
も、Furモジュールのメソッドなのですが、Clawモジュールが割り込んできたことでm_method_for_color
がオーバーライドされていることになります。
ここにきてやっと、モジュールとincludeを使って、メソッドを増やしている感がなんとなくわかりました。
モジュール(prepend編)
include
のイメージがつくと、こちらもすんなり理解できました。
Tigerクラスのinclude
部分をprepend
に変更して実行します。
Tigerクラスのファイル(修正)
require './fur'
class Tiger < Neko
#include Fur
prepend Fur # prependを使う
def instance_method
puts "instance method in Tiger"
end
end
実行ファイルは、msearch5.1.rb
をそのままです。
"クラス-->スーパークラス"の前に箱が入り込んでくるだけですね。
だんだんすんなり理解できるようになってきたかな。
複数モジュール(prepend編)
include
のイメージがつくと、こちらもやっぱりすんなり理解できました。
Pumaクラスのinclude
部分をprepend
に変更して実行します。
Pumaクラスのファイル(修正)
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個になっただけですね。
注意点はprependの表記の順番ですかね。
特異メソッド
「メソッドはクラスに属している」のならば、特異メソッドはどのクラスに属しているのか、疑問ですよね。
「オブジェクトの参照しているクラスに追加される」のであれば、他のオブジェクトからも呼び出せてしまうのでこれは違います。
たしかに!
こちらもメソッド探索をもとに考えるとすっきり考えられました。
実行ファイル:
# 特異メソッドを特定のオブジェクトに追加
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>
オブジェクトは必ず一つの特異クラスを持っている
上図の#neko4が特異クラスです。こういうクラスが存在するのを知らなかった。
クラスメソッド その1(アクシデントあり)
ラストです。ちょっとアクシデントがありました。
実行ファイル:
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
(1), (2), (4)は自分も期待通りの結果が出てよかったのですが、(3)がなぜundefined method
が表示されず困りました。
でも上でメソッド探索の"メ"ぐらいは理解できていたので、”もしかしてinstance_method”ってデフォルトで定義されている?とあたりを付けることができました。
クラスメソッド その2(アクシデント解決)
別途Inuクラスを作って、メソッド名に"inu_"を付けて確認してみました。
Inuクラス(Nekoクラスとほとんど一緒)
class Inu
class << self
def inu_class_method # メソッド名をユニークにしました。
"class method in Inu"
end
end
def inu_instance_method # メソッド名をユニークにしました。
"instance method in Inu"
end
end
実行ファイル:
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クラスでした。
これによってメソッド探索の最終の図も確認することができました。
まとめ
メソッド探索のまとめ
「レシーバから右へ一歩、そこからは上へ」
と表現しています。
この表現に尽きます。図を見ながらだとよくわかりました。
オブジェクトがレシーバでも「レシーバから右へ一歩、そこからは上へ」
クラスがレシーバでも「レシーバから右へ一歩、そこからは上へ」
ところで特異クラスは、どのようにしてプロの方々は使うのか?
ちょっとここまで調べるほど体力が残っていませんでした。
なにかメリットはあると思うので、デザインパターンなど次に調べてみたいと思います。
extendについて
requireとincludeを調べていたら、extendもあったことに気づき、併せてまとめてみました。
参考
【Ruby】includeとprependとextendの違いと用途
メソッド探索の例を理解すると、こちらも理解しやすそうです。
extendは、レシーバの特異クラスにModuleのメソッドを組み込む。
"extend = レシーバの特異クラスに対してModuleをincludeする"という感じですね。
確認
これをすると、extendでモジュールがどこに組み込まれているかがわかります。
今度は、Nekoクラスを継承したPantherクラスを使います。そこにTailモジュール(しっぽをイメージ)をextendして確認します。
Pantherクラスのファイル:
require './tail'
class Panther < Neko
extend Tail
def pan_instance_method
puts "instance method in Panther"
end
end
Tailモジュールのファイル:
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]
##まとめ
走り書きですが、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 の話 (または特異クラスとメタクラス)
なぜこう書けるのでしょうか。
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つ
- def self.method_name (特異メソッド形式)
- 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 と書けるのか。
同サイトの順番に沿ってコードを読むとよくわかりました。
- いつも通りのクラスを書く
クラス定義のシンタクス class を使って Bar クラスを定義する場合。
class Neko # いつもの書き方ですかね。
def hello
p 'hello'
end
end
neko = Neko.new
neko.hello #=> hello
- 'class'の書き方を'Neko = Class.new'に変更する
Class クラスのインスタンスを生成して、定数 Bar へ束縛する場合。
この場合はNekoへ束縛しています。
Neko = Class.new do # ここの書き方が違う!
def hello
p 'hello'
end
end
neko = Neko.new
neko.hello #=> hello
- 特異メソッド形式でNekoにメソッドを増やします
定数 Bar に入っている Class クラスのオブジェクトへ特異メソッドを定義してみましょう。
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
- 特異メソッド形式 → "<<" 形式に変更する
オブジェクトの特異クラスの引き出しの記法 << をあわせて考えると、最初のイディオムがやろうとしていることがわかってきます。
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が登場する理由がわかる。
- "<<" 部分をクラス定義の中に放り込む
せっかくなので、クラス定義をすべて class 記法での宣言の中に入れてみましょう。
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
おー、つながりましたー。
謝辞
参考にさせていただいたサイトは大変勉強になりました。本当に感謝です。