モジュールについて2点

RubyにはMix-inという制限付き多重継承を行う機能がある。
モジュールがそれを担っているわけだが、Mix-inは他のメジャーな言語では採用されていないので、ライトなRubyユーザはこの考え方には馴染みが無いのではなかろうか。
とかいう俺も、どうにもモジュールは持て余し気味だ。
とりあえず、これがどういうものなのかをきちんと把握することが使いこなすことへの近道だろう。
急がば回れ


1.
モジュールはちょっと前の記事で書いたように、メソッド定義を持ち、クラスにincludeすることで定義を移植することができる。
具体的にどういう仕組みでこれが実現されているのだろうか。
言語パズルのように細かい仕様をツツいたり、実装レベルで説明してもあまり意味はないので、最低限必要なことが理解できるような方向で。

module Fuga
  def foo
    p "foo"
  end
end

class Hoge
  def foo
    p "foo2"
    super
  end

  p ancestors  #(1) => [Hoge, Object, Kernel]
  include Fuga
  p ancestors  #(2) => [Hoge, Fuga, Object, Kernel]
end

Hoge.new.foo #(3) => "foo2"
             #    => "foo"

(注:Ruby1.8.7の結果。Ruby1.9.1だとKernelの後ろにBasicObjectがくっつくが、たいした問題ではない。)
上記のコードで、Fugaをincludeする前と後にancestorsメソッドを呼び出している。
class Hoge〜endの間のメソッド定義(def文)以外のコードは、ClassクラスのオブジェクトHogeをselfとして呼び出される。
ancestorsメソッドはClassクラスに定義されたメソッドで、Classオブジェクトから呼び出すことができ、そのクラスの上方向の継承関係を出力する。
(1)では[Hoge, Object, Kernel]となっており、Hogeクラスの上にObject、Kernelが存在することを示している。
ObjectはクラスでKernelはモジュール。RubyのすべてのクラスはObjectクラスから派生するので、上にObjectクラスがいるのは自然なことだ。
しかし、その上にObjectクラスにincludeされたKernelモジュールがいるのはどういうことだろう。


Rubyのモジュールは、includeすると継承階層的にクラスの上に挿入される。(2)のFugaをincludeした結果を見てもそのようになっている。
クラスの継承階層と同列に出力されていて、ancestorsの結果だけをみてもコレがクラスなのかモジュールなのかはわからない。
実際のところ、Ruby的にはこれがクラスなのかモジュールなのかはどうでもよく、継承階層としては同じように扱われる。
従って、(3)Hogeのメソッドfooの中でsuperを呼ぶと、一つ上の階層であるFugaのfooが呼ばれる。
つまりモジュールとは、クラスの継承階層の途中に後から強引に挟み込むことができる、継承専用のクラスなのだ、と考えることができる。


2.
モジュールの使い道は大きくわけて2通り。
上で見たようなincludeして使う、Mix-in用のクラスという使い方と、もう一つは名前空間的な使い方だ。

module Fuga
  class Hoge
    def foo
      p "foo"
    end
  end
end

temp = Hoge.new        #(1) => uninitialized constant Hoge (NameError)
temp = Fuga::Hoge.new
temp.foo               # => "foo"

include Fuga

temp = Hoge.new
temp.foo               # => "foo"

p Object.ancestors     # => [Object, Fuga, Kernel]

世の中のRubyのコードを見ると、モジュールの使い方としてはこっちの方が多いのではなかろうか。
トップレベルにクラスを定義すると、いろんなライブラリを使う場合に名前が衝突しがちだ。だからライブラリ名でモジュールを作り、その中にクラスを定義する。
上のコードの場合だと、モジュールFugaの中にHogeが定義されている。実際にはモジュールFugaに定数Hogeが定義され、Hogeクラスのオブジェクトが格納される。
トップレベルからはHogeは直接参照はできないので、(1)はエラーになるから、Fuga::Hogeという形でアクセスする。
こうすることでクラス名が衝突しないようにしているわけだ。
トップレベルでモジュールをincludeするとObjectクラスの上にFugaが挿入されて、Hogeがトップレベルで使えるようになる。
Objectクラスはすべてのクラスのスーパークラスだから、これで事実上コードのどこからでもHogeクラスが参照できる。


3.
ちなみにDXRubyは普通に使うとトップレベルにクラス・モジュールが定義されているように見えるが、実際の定義はDXRubyモジュール下となっている。
実はrequire時に勝手にトップレベルでDXRubyモジュールをincludeしている。
Rubyに詳しい人からすると「とんでもない」仕様なんじゃないかと思うのだが、Ruby初心者が使うことを考えると、とりあえずincludeしてください、というのは純粋にオマジナイでしかなく、自分で使ってても手間に感じるのでこのようにした。
includeしたくない人向けにrequire前に$dxruby_no_include=trueと書くとincludeされないという恐ろしい仕様もある。
とりあえず普通に使うのに手間は無く、必要とあらばincludeしないこともできるので、俺的には不満は無い。まあ、基本俺用だし。
これはスゴいアイデアだ!みんなこういうふうにしようぜ!とか言ってるわけではないのであしからずご了承くださいませ。