今まで、extend はクラス定義の中で呼ぶパターンにしか出会ってこなかった。
module M1
def bark
"bow wow"
end
end
class C1
extend M1
end
これをすると、
> C1.bark
=> "bow wow"
という具合。これだけを見ると、「extend はクラスメソッドを追加するもの」だったので、そう認識していた。
しかし、sinatraのソースコードに以下のような記述があった。
ここではまず、クラス定義内ではなくファイルのトップレベルで extend を呼んでいた。
最初に、Sinatra::Delegator とは、get や patch などの sinatra が提供するメソッドの呼び出しを、sinatra の主要クラスである Sinatra::Base クラスに移譲するモジュールらしい。つまり、このモジュールを mixin すると、sinatra が提供する get や patch などのメソッドをそのオブジェクトで呼び出せるようになるということ。
ファイルのトップレベルで extend するとどうなるのか?
ファイルのトップレベルで extend を直接呼んでいるということは、ファイルのトップレベルでself.extend を呼んでいるということ。そして、ファイルのトップレベルにおける self は、コードの実行主体である「 main オブジェクト」とのこと。そして、main オブジェクトはObject クラスのインスタンスらしい。
# こういうこと
<mainオブジェクト(Objectクラスのインスタンス)>.extend Sinatra::Delegator
インスタンスに extendってできるの?
今まで extend は、「モジュールに定義したメソッドをクラスメソッドとして挿入するためのもの」だと思っていた。それをクラスじゃなくてインスタンスに対して呼ぶとどうなるんだ?と思った。
extend は、「呼び出し元のオブジェクトの 特異クラス にモジュールを mixin すること」らしい。
全ての Ruby オブジェクトは何らかのクラスのインスタンスであり、自身のクラスは #class メソッドで呼び出せる。
それと同時に、「自身のみと一対一で対応するクラス」を #singleton_class メソッドで呼び出せるらしい1。それが特異クラス。
irb(main):031* class C2
irb(main):032> end
=> nil
irb(main):033> C2.class
=> Class
irb(main):034> C2.singleton_class
=> #<Class:C2>
irb(main):035* class C3
irb(main):036> end
=> nil
irb(main):037> C3.singleton_class
=> #<Class:C3>
#<Class:C2> #<Class:C3>が特異クラスということらしい。
あるオブジェクト a に対して extend を呼び出すと、そのオブジェクトの特異クラス(a.singleton_class)にモジュールを mixin する。そうすると、その特異クラスのシングルトンインスタンスである a でそのモジュールのメソッドや変数が使えるようになる。
一方で、そのオブジェクトの通常クラス(a.class)の他のインスタンスでは、そのモジュールのメソッドや変数は使えない。あくまで、a の特異クラスに mixin されているだけなので。
main オブジェクトに extend すると?
main オブジェクトはファイルのトップレベルに位置しているので、mainオブジェクトに extend すると、get や patch などのメソッドをファイルのトップレベルで呼べるようになる。
これによって get などを DSL っぽく扱える、ということだった。なるほど。
なんで include じゃダメなのか
コメントを見ると、「include だとモジュールを Object オブジェクトに入れてしまう。extend なら main オブジェクトだけにしか展開しない。」とある。
トップレベルで include を呼ぶと、Object クラスで include したのと同じことになるらしい。これは、mainオブジェクトに個別に includeメソッドが定義されており、これが内部で Object クラスへの include として処理してくれるかららしい。
全てのオブジェクトは Object クラスのインスタンスなので、include することによってあらゆるオブジェクトからそのメソッドが呼べるようになってしまい、あらゆるものを汚染してしまうとのこと。
extend を使うことによって、mainオブジェクトだけの特異クラスに mixin するので、他のオブジェクトは汚染せず、main オブジェクトだけでモジュールのメソッドが使えるようになるとのこと
まとめ・学んだこと
-
extendは、呼び出したオブジェクトの特異クラスにincludeを書くのと一緒 - 特異クラスは、そのオブジェクトをシングルトンインスタンスとして生成するそのオブジェクト専用のクラスで、全てのオブジェクトに対して存在する
- DSL っぽい構文は
mainオブジェクトへの mixin によって実現されていた - Ruby ではコードを実行するのもオブジェクト、全部がオブジェクト
-
extendは全ての Ruby オブジェクトに対して呼べるが、includeはクラスとモジュールとmainオブジェクトに対してしか呼べない2- 正確には
Moduleクラスのインスタンスでしか呼べない - モジュールは
Moduleクラスのインスタンスで、クラスはModuleクラスを継承したClassクラスのインスタンス
- 正確には