疑問に思ったことをググってコード書いて試してみた。
- 成果物
- 情報源
- ソースコードで書いてみる
- 所感
- 対象環境
成果物
情報源
ソースコードで書いてみる
前回で概念はなんとなくわかった。でも実際に書いてみないとよくわからない。以下を参考にするとわかりやすい。
というわけでやってみる。
- クラスを定義する
- インスタンスを生成する
- メソッドを呼ぶ
- クラスを継承する
- 親メソッドを呼ぶ
- 自メソッドを呼ぶ
- 同名なら自メソッドが呼ばれる
- 特異クラス
- モジュールを定義する
- 定数を定義する
- インスタンスメソッドを定義する
- モジュール関数を定義する
- クラスメソッドを定義する
- 優先順位
- モジュールを名前空間として使う
- 定数を定義する
- 定数を参照する
- モジュールをクラスへMixinする
include
prepend
extend
- 継承リスト
Module#ancestors
- モジュールをネストする
1. クラスを定義する
class C end
class C; end
1-1. インスタンスを生成する
class C; end c = C.new
1-2. メソッドを呼ぶ
class C; def m; :m; end; end C.new.m
2. クラスを継承する
class C; def m; :m; end; end class D < C; end
2-1. 親メソッドを呼ぶ
class C; def m; :m; end; end class D < C; end D.new.m
2-2. 自メソッドを呼ぶ
class C; def m; :m; end; end class D < C; def n; :n; end; end D.new.n
2-3. 同名なら自メソッドが呼ばれる
class C; def m; :m; end; end class D < C; def m; :n; end; end D.new.m #=> :n
3. 特異クラス
特異クラスとは特異メソッドをもつクラスである。各オブジェクトが必ず1つ持っている。singleton_class
で参照できる。
特異メソッドとは、特定のインスタンスだけがもつメソッドである。
class C; def m; :m; end; end c = C.new d = C.new c.m #=> :m d.m #=> :m def d.m; :n; end; # 特異メソッド c.m #=> :m d.m #=> :n
> C.singleton_class => #<Class:C> > c.singleton_class => #<Class:#<C:0x02450670>> > d.singleton_class => #<Class:#<C:0x02464020>>
特異クラスは後述する継承リストModule#ancestorsを持っている。その優先度は最優先である。
> c.singleton_class.ancestors => [#<Class:#<C:0x02450670>>, C, Object, PP::ObjectMixin, Kernel, BasicObject] > d.singleton_class.ancestors => [#<Class:#<C:0x02464020>>, C, Object, PP::ObjectMixin, Kernel, BasicObject]
4. モジュールを定義する
module M end
module M; end
4-1. 定数を定義する
module M MAX = 100 end
M::MAX
4-2. インスタンスメソッドを定義する
module M def m; :m; end end
module M; def m; :m; end; end
M::m : undefined method `m' for M:Module (NoMethodError)
M.new.m : undefined method `new' for M:Module (NoMethodError)
M.m : undefined method `m' for M:Module (NoMethodError)
モジュールのインスタンスメソッドは呼び出せない。というよりモジュールはインスタンス生成できない。
だったらインスタンスメソッドという名前がおかしい気がするのだが。
4-3. モジュール関数を定義する
module M def m; :m; end module_function :m end
module M; def m; :m; end; module_function :m; end
module_function
に渡したメソッドは呼び出せるようになる。
M::m #=> :m
M.m #=> :m
M.new.m : undefined method `new' for M:Module (NoMethodError)
ほかのプログラミング言語でいうところのスタティック・メソッドだろう。
4-4. クラスメソッドを定義する
module M def self.m; :m; end end
module M; def self.m; :m; end; end
メソッド定義するとき名前の前にself.
を付与する。
M::m #=> :m
M.m #=> :m
M.new.m : undefined method `new' for M:Module (NoMethodError)
4-5. 優先順位
同名のモジュール関数とクラス関数があったとき、先に呼ばれるのはどちらか。答えは後に定義したほう。
module M def self.m; p 'c'; end def m; p 'm'; end module_function :m end
M.m #=> "m"
module M def m; p 'm'; end module_function :m def self.m; p 'c'; end end
M.m #=> "c"
5. モジュールを名前空間として使う
5-1. 定数を定義する
module M MAX = 100 end
5-2. 定数を参照する
module M MAX = 100 end M::MAX
6. モジュールをクラスへMixinする
6-1. include
module M def m; :m; end end class C include M end C.m #=> undefined method `m' for C:Class (NoMethodError) C::m #=> undefined method `m' for C:Class (NoMethodError) C.new.m #=> :m
クラス内でinclude モジュール名
とすると、そのモジュールのインスタンスメソッドがクラスに組み込まれる。
複数include
したときは、後にinclude
されたほうが優先して呼ばれる。
module M def m; :m; end end module N def m; :n; end end class C include M include N end C.new.m #=> :n
include
と自クラスなら自クラスが優先される。
module M def m; :m; end end class C include M def m; :c; end end C.new.m #=> :c
6-2. prepend
自分の同名メソッドよりもモジュールのメソッドを優先する。
module M def m; :m; end end class C prepend M def m; :c; end end C.new.m #=> :m
include
とprepend
のときでも同じくprepend
が優先。
module M def m; :m; end end module N def m; :n; end end class C prepend M include N end C.new.m #=> :m
module M def m; :m; end end module N def m; :n; end end class C include N prepend M end C.new.m #=> :m
さらにクラスのインスタンスメソッドがあってもprepend
が最優先。
module M def m; :m; end end module N def m; :n; end end class C prepend M include N def m; :c; end end C.new.m #=> :m C.m #=> : undefined method `m' for C:Class (NoMethodError) C::m #=> : undefined method `m' for C:Class (NoMethodError)
6-3. extend
クラスメソッドとして組み込まれる。
include
とprepend
はインスタンスメソッドとして組み込まれた。それに対してextend
はクラスメソッドになる。
module M def m; :m; end end class C extend M end p C.ancestors #=> [C, Object, Kernel, BasicObject] p C.singleton_class.ancestors #=> [#<Class:C>, M, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject] C::m #=> :m C.m #=> :m C.new.m #=> : undefined method `m' for #<C:0x024b0b68> (NoMethodError)
自クラスとextend
ならextend
が優先される。
class D extend M def m; :d; end end p D.ancestors #=> [D, Object, Kernel, BasicObject] p D.singleton_class.ancestors #=> [#<Class:D>, M, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject] p D::m #=> :m p D.m #=> :m class E def m; :e; end extend M end p E.ancestors #=> [E, Object, Kernel, BasicObject] p E.singleton_class.ancestors #=> [#<Class:E>, M, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject] p E::m #=> :m p E.m #=> :m
後述する継承リストの順としてはextend
されたモジュールのほうが自クラスより低いように見えるのだが? なのになぜextend
されたモジュールが優先して呼び出されるのだろう。謎。
extend
は自クラスのancestors
でなくsingleton_class
のancestors
に追加される。それが関係しているのだろうか。でもsingleton_class
のancestors
をみてもそのリストの最初は自クラスにみえるんですけど? モジュールはその次にみえるんですけど? なのになぜモジュールのメソッドが優先されたの? わからん。
以下、公式ドキュメントを読んでもわからんかった。
引数で指定したモジュールのインスタンスメソッドを self の特異メソッドとして追加します。 Module#include は、クラス(のインスタンス)に機能を追加しますが、extend は、ある特定のオブジェクトだけにモジュールの機能を追加したいときに使用します。 引数に複数のモジュールを指定した場合、最後の引数から逆順に extend を行います。
「特異メソッドとして追加する」というのがポイント。そしてself
はクラスのことだと思われる。ほかのプログラミング言語だとインスタンスを指すので紛らわしい。よくわからなかったのが以下の「特定のオブジェクト」というところ。
機能 | 概要 |
---|---|
include |
クラスのインスタンスに追加する |
extend |
特定のオブジェクトに追加する |
「特定のオブジェクト」ってなんぞ? 最初に「特異メソッドとして追加します。」って書いているよね? それってつまり特異クラスに追加するってことじゃないの?
extend
って「指定したクラスオブジェクトがもつ特異クラスの特異メソッドとして、モジュールのインスタンスメソッドを追加する」だと解釈しているのだが。違うのかな? だってソースコードもC.singleton_class.ancestors
でモジュールが追加されているじゃん。
そのことを「特定のオブジェクトに追加する」と表現しているだけなのかな? 特異クラスはオブジェクトごとに1つだけ持っているものだから、そう表現できなくもないか。以下のようにも書いていたし。
extend の機能は、「特異クラスに対する Module#include」と言い替えることもできます。ただしその場合、フック用のメソッドが Module#extended ではなく Module#included になるという違いがあります。
フック用のメソッドがーとかいう後半はなにをいっているのかわからんけども。ていうかextend
の説明なのに「extend
ではなく」とか言われてもね。何言ってるかわからんですわ。
で、「クラスがもつ特異クラス」ってどれ? 以下のリストの中になくない? 最初のは単なる自クラスだし、2番目はモジュール。特異クラスは最優先で1番目にあるはずだけど、なくね?
p E.singleton_class.ancestors #=> [#<Class:E>, M, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
インスタンスの特異クラスならあったけど。
p E.new.singleton_class.ancestors #=> [#<Class:#<E:0x0060f5b8>>, E, Object, Kernel, BasicObject]
でも参照しているのはクラスはなず。インスタンスではない。
p E::m #=> :m p E.m #=> :m
だからクラスオブジェクトの特異クラスを参照しているはず。そしてクラスオブジェクトの特異クラスは、ancestors
の中にないようにみえる。なのに呼ばれているのはextend
されたモジュール。お前は2番目だろうに。なぜだ。自クラスにも同名メソッドm
がある。そして自クラスはancestors
の先頭。ならば自クラスのm
メソッドが呼ばれるべきなんじゃないの? わからん!
7. 継承リストModule#ancestors
クラス、モジュールのスーパークラスとインクルードしているモジュールを優先順位順に配列に格納して返します。
ancestors
は「祖先」という意味。
継承したりMixin(include
,prepend
,extend
)したらこのancestors
にその型が入る。継承された順である。
p Integer.ancestors
[Integer, Numeric, Comparable, Object, PP::ObjectMixin, Kernel, BasicObject]
BasicObject ↑ Kernel ↑ PP::ObjectMixin ↑ Object ↑ Comparable ↑ Numeric ↑ Integer
7-1. メソッド呼出優先順序
Module#ancestorsは継承順にしてメソッド呼出優先順序でもある。
リストの先頭から同一名のメソッドを探索し、最初にみつかったメソッドを呼び出す。なので、同じ名前のメソッドが複数あったとき、リストの前の方にあるやつが呼び出される。
7-2. 優先度=特異クラス<prepend
<自クラス<include
class C; end C.ancestors
[C, Object, PP::ObjectMixin, Kernel, BasicObject]
class C; def m; :c; end; end C.ancestors C.new.m #=> :c
m
メソッドを探す。最初はModule#ancestorsの先頭にあるC
クラスから。まさにそのクラスで定義されているので、それを実行する。
[C, Object, PP::ObjectMixin, Kernel, BasicObject]
次にinclude
してみる。
module M; def m; :m; end; end class C; include M; def m; :c; end; end C.ancestors C.new.m #=> :c
m
メソッドを探す。最初はModule#ancestorsの先頭にあるC
クラスから。そのクラスで定義されているので、それを実行する。:c
を返す。
=> [C, M, Object, PP::ObjectMixin, Kernel, BasicObject]
次にprepend
してみる。
module M; def m; :m; end; end class C; prepend M; def m; :c; end; end C.ancestors C.new.m #=> :c
m
メソッドを探す。最初はModule#ancestorsの先頭にあるM
モジュールから。そのモジュールで定義されているので、それを実行する。:m
を返す。
=> [M, C, Object, PP::ObjectMixin, Kernel, BasicObject]
ようするにinclude
,prepend
,継承は、単にModule#ancestorsへクラスを挿入する操作であるともいえる。
もし優先順序がわからなくなったらModule#ancestorsを見ればいい。
ではこれまでの全要素をぶちこんで同名メソッドを作ってみよう。
- 自クラス
- 親クラス
- 特異クラス
- Module(
include
,prepend
,extend
)
#!/usr/bin/env ruby module M def m; :mi; end def self.m; :mc; end end module N def m; :ni; end def self.m; :nc; end end module O def m; :oi; end def self.m; :oc; end end class C def m; :c; end def self.m; :cc; end end class D < C prepend M include N extend O def m; :d; end def self.m; :cd; end end p C.ancestors p C::m p C.m p C.new.m p D.ancestors p D::m p D.m p D.new.m d = D.new e = D.new p d.singleton_class.ancestors p e.singleton_class.ancestors p d.m p e.m def e.m; :e; end p e.singleton_class.ancestors p d.m p e.m p e.singleton_class.ancestors[0].methods p e.singleton_class.ancestors[0].method :m p (e.singleton_class.ancestors[0].method :m).call p e.singleton_class.m e.extend O p e.singleton_class.ancestors p e.m p e::m
[C, Object, Kernel, BasicObject] :cc :cc :c [M, D, N, C, Object, Kernel, BasicObject] :cd :cd :mi [#<Class:#<D:0x00e0f280>>, M, D, N, C, Object, Kernel, BasicObject] [#<Class:#<D:0x00e0f250>>, M, D, N, C, Object, Kernel, BasicObject] :mi :mi [#<Class:#<D:0x00e0f250>>, M, D, N, C, Object, Kernel, BasicObject] :mi :e [:m, :allocate, :superclass, :new, :<=>, :<=, :>=, :==, :===, :included_modules, :include?, :ancestors, :attr, :attr_reader, :attr_writer, :attr_accessor, :instance_methods, :public_instance_methods, :protected_instance_methods, :private_instance_methods, :constants, :const_get, :const_set, :const_defined?, :freeze, :inspect, :const_source_location, :const_missing, :class_variable_get, :class_variable_defined?, :remove_class_variable, :class_variable_set, :class_variables, :public_constant, :private_constant, :deprecate_constant, :singleton_class?, :<, :prepend, :>, :include, :module_eval, :class_eval, :remove_method, :undef_method, :to_s, :module_exec, :class_exec, :alias_method, :protected_method_defined?, :public_class_method, :method_defined?, :public_method_defined?, :private_method_defined?, :private_class_method, :public_instance_method, :name, :autoload?, :instance_method, :define_method, :autoload, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :class, :frozen?, :then, :public_send, :method, :public_method, :singleton_method, :tap, :define_singleton_method, :extend, :clone, :yield_self, :to_enum, :enum_for, :=~, :!~, :nil?, :eql?, :respond_to?, :object_id, :send, :display, :hash, :singleton_class, :dup, :itself, :!, :!=, :equal?, :instance_eval, :instance_exec, :__id__, :__send__] #<Method: #<Class:#<D:0x00f2b338>>(D).m() ./c.rb:23> :cd :cd :cd [#<Class:#<D:0x005bb240>>, O, M, D, N, C, Object, Kernel, BasicObject] :e :e
インスタンスメソッドの優先度は次のとおりである。
[#<Class:#<D:0x00e0f250>>, M, D, N, C, Object, Kernel, BasicObject]
- 特異クラス
prepend
- 自クラス
include
- 親クラス
extend
がよくわからない。クラス定義内でextend
したらancestors
に入ってなかった。クラスメソッドとしてMixinされるようだが、D::m
のように呼び出しても:cd
となり自クラスメソッドが呼び出されている。O
モジュールのメソッドは呼ばれないのか? 優先度が低い? いや、そもそもancestors
の中にO
モジュールがない。
class C extend O end
# O がないよ! [#<Class:#<D:0x00e0f250>>, M, D, N, C, Object, Kernel, BasicObject]
インスタンスからextend
メソッドを呼び出すとancestors
に入っていた。
e.extend O
どゆこと? 上記コードは同じことをやっているんじゃないの? e.extend O
したときのancestors
は以下。
[#<Class:#<D:0x005bb240>>, O, M, D, N, C, Object, Kernel, BasicObject]
- 特異クラス
expand
prepend
- 自クラス
include
- 親クラス
以下をみてみると特異クラスの中にMixinされるようだが。
わからん!
8. モジュールをネストする
#!/usr/bin/env ruby module Name module M def m; :m; end end end class C; include Name::M; end p C.new.m
所感
特異クラス、特異メソッド、extend
あたりが怪しい。とくにextend
について。
- どんな記述方法があるのか
- その効果に違いはあるのか
まさかと思うが、include
やprepend
についてもメソッドがあって、class内に書いたときと動作が違ったりするんじゃないの?
もう疲れたよパトラッシュ。たのむから断片的な情報でなくまとめてくれや。ググッて断片情報みつけて試してこれまでのルールぶち壊されての繰り返しとか、勘弁して。ちゃんと勉強させてよ公式ドキュメント。
対象環境
- Raspbierry pi 4 Model B
- Raspberry Pi OS buster 10.0 2020-08-20 ※
- bash 5.0.3(1)-release
- Ruby 3.0.2
$ uname -a Linux raspberrypi 5.10.52-v7l+ #1441 SMP Tue Aug 3 18:11:56 BST 2021 armv7l GNU/Linux