Rubyで自作の外部モジュールを読み込む方法 - include と extend と module_function - (゚∀゚)o彡 sasata299's blogを読んでて、もしかしたらちょっと誤解があるのかなと思ったのでrequireとincludeとextendの話を。
requireはKernelモジュールのメソッド
Rubyで外部ライブラリを読み込むには、require を利用します。
Rubyで自作の外部モジュールを読み込む方法 - include と extend と module_function - (゚∀゚)o彡 sasata299's blog
これはその通り。もっと具体的に言うと、requireは引数のファイル名のRubyファイルを読み込んで実行するメソッドです。引数が絶対パスだったときはそのファイルを、そうでない場合はロードパスを優先順位上位から辿って最初に見つかったファイルをロードする。で、拡張子まで含めて書いてある場合はそのファイルを、そうでない場合は.rbもしくは.soのものをロードするそうな。requireの他にloadというメソッドもあってこれはやることは同じだけど、requireの場合読み込むのは一度だけだけど、loadの場合は無条件に読み込む。なのでライブラリとかを読み込むときはrequireを使う。
includeはModuleクラスのメソッド
ただ、モジュールを読み込むときには include しないといけない、ということを「初めてのRuby」を呼んで知ったので早速試してみました。
Rubyで自作の外部モジュールを読み込む方法 - include と extend と module_function - (゚∀゚)o彡 sasata299's blog
これは若干間違い。includeはモジュールを使うよって宣言じゃないので、
散々調べた結果、どうも include ってそのファイルの読み込みはしてくれないっぽいですね。あくまでもファイルの読み込みは require が担当っぽいです。
Rubyで自作の外部モジュールを読み込む方法 - include と extend と module_function - (゚∀゚)o彡 sasata299's blog
っていうのが正しい。
じゃあincludeって何なんだよってことなんだけど、これは「引数にモジュールを取り、includeしたクラスにincludeされたモジュールの性質を追加する」ということをしてくれる、というModuleクラスのメソッド。例えば、あるクラスBarがFooモジュールをincludeすると、スーパークラス(Barが何も継承してなければ暗黙のスーパークラスObject)との継承関係の間にFooモジュールを差し込んで、メソッドやクラス変数を探すときに「Barを見る→無かったらFooを見る→無かったらObjectを見る→無かったらKernelを見る→それでも無いなら無い」という風にしてくれる。つまりこういうこと。
class Bar include Foo end Bar.ancestors # ancestorsはスーパークラスとインクルードしてるモジュールを優先順位順に返すメソッド # => [Bar, Foo, Object, Kernel]
で、モジュールを定義したりincludeしたりすると、こういうことになる。
- Hogeモジュールの中でself.hogeで定義されたメソッドは、Hoge.hogeとして使える
- Hogeモジュールの中でfugaとして定義されたメソッドは、Hoge.fugaとしては使えない
- includeすると、includeしたクラスのインスタンスからはfugaが使えるようになる
これは普通の継承関係にあるサブクラスとスーパークラスのことを考えると何のことはない。
- def self.hogeはクラスにhogeというメソッドを定義する
- def fugaはそのクラスのインスタンスにfugaというメソッドを定義する
- クラスに定義されたhogeメソッドはKlass.hogeという形で呼べる
- スーパークラスで定義されたfugaインスタンスメソッドはサブクラスのインスタンスからはfugaという形で呼べる
で、この「継承」の部分を「includeする」に読み換えるとあるクラスからモジュールをincludeしたときの挙動は分かると思う。同じように振る舞う。
トップレベルでのinclude
で。ここまでは「クラスがモジュールをincludeしたとき」の話。こっから先はちょっとややこしい。元記事ではトップレベルでモジュールをincludeしてるんだけど、これはちょっと面白いことになる。
トップレベルって何なんだって話なんだけど、トップレベル、つまりclass文やmodule文やブロックの中じゃない、一番外側で書いたRubyコードは、mainという暗黙のObjectクラスのインスタンスがselfである。なので、トップレベルで何らかのメソッドを呼び出すと、self.method、つまりmainオブジェクトのメソッドを呼び出す形になる。で、ここが微妙なんだけど、じゃあトップレベルでincludeとかやったらdef self.hogeで定義したやつがトップレベルでhogeになり、def fugaで定義したやつがmainオブジェクトのインスタンスのfugaになりそうなものだけど、そうはならない。大体mainオブジェクトはObjectクラスのインスタンスなのに、それのさらにインスタンスって何だよ意味わからんよ、って話ですよ。
実際はどうなってるかというと、def self.hogeで定義したメソッドは全てのクラスのクラスメソッドになり、def hogeで定義したメソッドは全てのインスタンスメソッドになる。なので、元記事のコードのhelloはめでたくObjectクラスのインスタンスであるところのmainオブジェクトのメソッドになりましたし、debugはZozom.debugとして呼べるのみならず別なクラスを作っても例えばYakitori.debugみたいに呼べる、という話になる。ややこしい。
mainオブジェクトからincludeする
これは何なんだ、一体どういうことだ…俺は、何かとんでもない謎に踏み込んでしまったのか…と言いたいところだけど、まぁ実は簡単な話だったりする。class文の中のincludeはModuleクラスのクラスメソッドであるところのincludeだけど、トップレベルのincludeは微妙に特殊で、トップレベルのincludeメソッドつまりmainオブジェクトのincludeメソッドを実行するとObjectクラスのincludeクラスメソッドを呼んだような挙動をする。そのために、次のようなことになる。
- 全てのクラスはObjectクラスのサブクラスなので(1.9系は例外もあるけど、概ねそうだと思っていいので)、includeしたモジュールの中でdef self.hogeとして定義されていたメソッドがクラスメソッドとして使える
- mainオブジェクトはObjectクラスのインスタンスなのでincludeしたモジュールの中でdef fugaとして定義されていたメソッドがインスタンスメソッドとして使える、そのためfugaが組み込み関数のように見えるようになった
長くなってきたので一旦まとめる
- Rubyファイルを読み込むのはrequire
- includeは「モジュールを使うよって宣言」ではなくて「モジュールが持ってる性質を、クラスに組み込むメソッド」
- class/module文の中で使うと、モジュールの特徴をそのクラスに追加する
- モジュールの中でdef self.hogeとして定義されてたやつはそのクラスのクラスメソッドになる
- モジュールの中でdef fugaとして定義されてたやつはそのクラスのインスタンスメソッドになる
- class/module文の外で使うと、モジュールの特徴をObjectクラスに追加する
- モジュールの中でdef self.hogeとして定義されてたやつは全てのクラスのクラスメソッドになる
- モジュールの中でdef fugaとして定義されてたやつは全てのクラスのインスタンスメソッドになる
調べてたらincludeが思いのほかややこしくて説明が長くなっちゃったので、module_funcfionとextendは次の記事に続く。
ちょっとだけ補足
def self.hogeという形式のメソッド定義は、主にクラスメソッドの定義として使われてるけど、正確に言うと「特異メソッド」というのを定義するもの。なのでselfの部分は実はどんなオブジェクトでもあまつさえ式でもいいし、クラスメソッドと呼んでるものの実態は「"クラスという種類のオブジェクト"の特異クラスの(インスタンス)メソッド」だったりする。はず。確か。特異クラスとか特異メソッドとかは…次の記事で書くかな。多分。