RubyのModuleについて

Rubyを勉強し始めてマニュアルを眺めていたとき、どうしても理解できなかったことがある。
それはModuleで、クラスにincludeした場合にいったいどういう理屈で何が起こっているのかがわからなかった。別に内部的な話ではなく、たとえばincludeとextendの違いとか、そういう感じのことだ。
今回はこの間の記事では前置きでさらっと流してしまった部分、Moduleについて。
あ、そうそう。俺は別にRubyに詳しい人でもなくて、完全に理解できているわけではないから、間違っている可能性がおおいにある。なんとなく理解できた気がしたなら、実際に自分でコードを書いて検証するのがいいと思う。
俺の記事ではせいぜい、理解の壁になっている部分に小さなヒビを入れる程度にしかならないはずだ。


1.
まず整理しよう。
Rubyはすべてがオブジェクトとなっていて、オブジェクトはそれぞれ自分自身をselfに切り替えるインスタンスメソッドを持っている。Rubyにおいてメソッドと呼ばれるものは、すべて何らかのオブジェクトのインスタンスメソッドである。
ClassオブジェクトにはClassクラスで定義されたメソッドがいろいろと備わっているが、このうちのnewメソッドは、そのClassオブジェクトに定義されたメソッドを新しく作ったオブジェクトにくっつける。
・・・いきなりよくわからないことになってきたが、これは意図的なものである。


2.
上記の文章やRubyリファレンスのModuleのところを読んだときに「?」となる原因は、メソッドという言葉に2つの意味が含まれているからだ。
(a)すべてのオブジェクトが持つ、呼び出し可能なインスタンスメソッドと、(b)Classオブジェクトが持つ、newで生成したオブジェクトにくっつけるためのデータとしてのメソッド定義、だ。
Rubyはすべてがオブジェクトであるから、(a)のメソッドはすべてのオブジェクトが持っている。普通にメソッドと呼ばれる。
(b)のほうはClassオブジェクトが保持しているデータであり、そのデータを作る機能はClassクラスにある。Rubyリファレンスでは「クラス/モジュールに定義されたメソッド」という言い回しで表現される。
困ったことにどっちもメソッドだ。
この記事では、(a)をインスタンスメソッド、(b)をメソッド定義と呼ぶことにする。これが合っているかどうかはわからないが、とりあえず言葉を定義しないと始まらない。


3.
Classオブジェクトにメソッド定義を追加するには以下のようなコードを書く。

class Hoge
  def foo
    p "a"
  end
end
Hoge.foo # => undefined method `foo' for Hoge:Class (NoMethodError)
obj = Hoge.new
obj.foo   # => "a"

定数HogeにClassオブジェクトが格納され、そのClassオブジェクトにメソッド定義fooが追加される。
このfooはタダのデータとしてのメソッド定義であるから、Hogeから呼び出すことはできない。
インスタンスメソッドnewを呼び出すとHogeオブジェクトが生成されて、Hogeクラスが持つメソッド定義がHogeオブジェクトのインスタンスメソッドとして呼べるようになる。


4.
Moduleオブジェクトを生成してメソッド定義を作るには以下のようなコードを書く。

module Fuga
  def foo
    p "a"
  end
end
Fuga.foo       # => undefined method `foo' for Fuga:Module (NoMethodError)
obj = Fuga.new # => undefined method `new' for Fuga:Module (NoMethodError)

ClassクラスはModuleクラスから派生したクラスで、メソッド定義を作る機能は実際にはModuleクラスの方にある。
しかし、Moduleオブジェクトにはインスタンスメソッドnewが無い(Classクラスに定義されてる)ので、fooを呼び出せるオブジェクトを生成することはできない。
Rubyのモジュールは、メソッド定義を持つだけの、いわばデータの入れ物である。


ここまで前置き。
ここから本題。


5.
Moduleオブジェクトが持っているメソッド定義はそのままでは使えない。
主な用途は、newできるClassオブジェクトにメソッド定義を追加することである。これにはincludeを使う。

module Fuga
  def foo
    p "a"
  end
end

class Hoge
  include Fuga
end

Hoge.foo        # => undefined method `foo' for Hoge:Class (NoMethodError)
obj = Hoge.new
obj.foo         # => "a"

class〜endの中のスコープではselfはそのクラス、すなわちHoge(=Classオブジェクト)となっているので、includeメソッドのレシーバはHogeである。
includeメソッドはModuleクラスに定義されているので、ClassオブジェクトであるHogeでも呼び出すことができる。
このメソッドの動作は、引数で渡されたModuleオブジェクトが持つメソッド定義を、ごっそり自分に移植する。
これが、includeだ。
Rubyの実装の話になるともう少し違うもの(化身クラスとか)が出てくるが、たぶん(俺が)理解不能になるのでここでは触れない。


6.
includeメソッドはメソッド定義をselfに移植する。
中身は問わない。
たとえばインスタンス変数のアクセスなんかも、Moduleオブジェクトのメソッド定義に書くことができる。

module Fuga
  def foo
    p @bar
  end
end

class Hoge
  include Fuga
  def initialize
    @bar = "baz"
  end
end

obj = Hoge.new
obj.foo        # => "baz"

Moduleオブジェクトのメソッド定義はそのままでは実行できないから、何を書こうが自由だ。実行時に整合性が取れてさえいればよい。
この場合のFugaは、「includeするほうでfooを呼ぶ前に@barに値を設定しておくこと」という取り決めが存在する、というだけだ。
includeしたクラス側では、Moduleオブジェクトのメソッド定義は完全に自分のメソッド定義のように扱える。
ちなみにincludeはModuleクラスで定義されているので、Moduleオブジェクトでも使うことができる。

module Fuga
  def foo
    p @bar
  end
end

module Piyo
  include Fuga
end

class Hoge
  include Piyo
  def initialize
    @bar = "baz"
  end
end

obj = Hoge.new
obj.foo        # => "baz"

あまり使うことは無いだろうが、理屈としては確かにそうであり、筋は通っている。できないと困る場面もありそうだ。
逆に、クラスをincludeすることはできない。
Rubyでは機能を制限した多重継承としてMix-in(Moduleオブジェクトをincludeする考え方)を採用していて、このあたりのできる/できないはそっちの概念による制限・設計である。


7.
最後に、extendメソッドについて。
このように使う。

module Fuga
  def foo
    p @bar
  end
end

class Hoge
  def initialize
    @bar = "baz"
  end
end

obj = Hoge.new
obj.extend Fuga
obj.foo        # => "baz"

extendはObjectクラスに定義されたメソッドで、オブジェクトにモジュールのメソッドを特異メソッドとして追加する。
別の言い方をすると、Moduleオブジェクトのメソッド定義をselfの特異クラスに移植する。
つまり上のコードは以下と同義である。

module Fuga
  def foo
    p @bar
  end
end

class Hoge
  def initialize
    @bar = "baz"
  end
end

obj = Hoge.new

class << obj
  include Fuga
end

obj.foo        # => "baz"


8.
Rubyの仕組みと動きをきちんと理解すればRubyを自由自在に操ることができるし、自由自在に操ることができるというのは=メタプログラミングが自然にできる、ということになる。
必要なのはRubyの原理の理解であり、そのほとんどは言語レベルから見えていて、見えている部分が理解できるだけで自由度はかなり広がるはずだ。