light log

学んだこととか

Rubyのモジュールについて調べた

ちょっと前にモジュール関係で結構ハマって、なんでこれが動かないんだろうってのがあったので少し調べた。結局それはモジュール関係なくただの自分の(くそしょぼい)凡ミスのせいだったんだけど、せっかくちょっと調べたのでメモしておく。

はじめに

まずirbでこうやると、

$ irb
irb(main):001:0> Class.superclass
=> Module
irb(main):002:0> Module.superclass
=> Object

ModuleはClassのスーパークラスだとわかる。

つまり、ModuleはClassのもっと一般的なやつ。言い換えると、特殊なModuleがClass。

クラスとモジュールの違いは、リファレンスマニュアルを見ると、こう書かれてる。

クラスとモジュールには

  • クラスはインスタンスを作成できるが、モジュールはできない。
  • モジュールを他のモジュールやクラスにインクルードすることはできるが,クラスをインクルードすることはできない。

という違いがありますが、それ以外のほとんどの機能は Module から継 承されています。Module のメソッドのうち

  • Module#module_function
  • Module#extend_object
  • Module#append_features

は Class では未定義にされています。

class Class - Ruby 2.2.0 リファレンスマニュアル

以下の特性を見ると、モジュールはあくまでクラスの機能を補助(拡張)するものというイメージ。

  • モジュールはインスタンスを生成できない
  • モジュールは他のクラスやモジュールにincludeして使われる

一番シンプルな例。

module M
  def foo
    'module method'
  end
end

class C
  include M
end

puts C.new.foo
#=> module method

Rubyでのモジュールの役割

以下二つの役割がある。

Mix-in

クラスやモジュールにモジュールをインクルードすることをMix-inと呼ぶ。

Rubyのクラスは多重継承できないが、Mix-inなら複数のモジュールをインクルードすることができる。

module M1
  def foo
    'method foo'
  end
end

module M2
  def bar
    'method bar'
  end
end

class C
  include M1
  include M2
end

puts C.new.foo
puts C.new.bar
#=> method foo
#=> method bar

ずっとインクルードって書いてきたけど、モジュールを他のクラスやモジュールに取り込む機能としてはextendもある。

instance method Object#extend

モジュールのincludeが、指定したモジュールのインスタンスメソッドをクラス(やモジュール)のインスタンスメソッドとして追加するのに対し、extendはクラス(やモジュール)の特異メソッドとして追加する。

つまり、クラスにモジュールをextendすると、モジュールに定義されたメソッドをクラスメソッドとして使えるようになる。

module M1
  def foo
    'method foo'
  end
end

# モジュールにextend
module M2
  extend M1
end

# クラスにextend
class C
  extend M1
end

puts M2.foo
puts C.foo
#=> method foo
#=> method foo

名前空間

クラス定義をモジュールで囲むことで、名前空間として利用できる。異なる名前空間であれば、同名のクラスも定義できる。

module M1
  class Foo
    def foo
      'foo'
    end
  end
end

module M2
  class Foo
    def bar
      'bar'
    end
  end
end

puts M1::Foo.new.foo
puts M2::Foo.new.bar
#=> foo
#=> bar

その他もろもろ

以上が基本。当然もっと高度なトピックもある。

以下、調べてるときに出くわしたものの一部。さわりだけ。

クラスとの使いわけ

クラスAとクラスBに共通する機能を、クラスCにまとめて両クラスのスーパークラスとするのか、モジュールMにまとめて両クラスでインクルードするのかっていう判断は割と難しい(場合がある)と思う。たぶん、is-a関係とかhas-a関係とかを考慮に入れて判断するんだろう。

優先順位

インクルードしたモジュールにスーパークラスと同名のメソッドが定義されていた場合、モジュールが優先される。インクルードしたモジュールはサブクラスとスーパークラスの間に挟まれる継承関係になる。

module M1
  def foo
    'module foo'
  end
end

class A
  def foo
    'class foo'
  end
end

class B < A
  include M1
end

puts B.new.foo
#=> module foo

puts B.ancestors.join(' -> ')
#=> B -> M1 -> A -> Object -> Kernel -> BasicObject

複数のモジュールをインクルードした場合は、後でインクルードしたモジュールのメソッドが優先。

module M1
  def foo
    'M1 foo'
  end
end

module M2
  def foo
    'M2 foo'
  end
end

class A
  include M1
  include M2
end

puts A.new.foo
#=> M2 foo

instance method Module#include - Ruby 2.2.0 リファレンスマニュアル

module_function

メソッドをモジュール関数にします。

モジュール関数とは、プライベートメソッドであると同時に モジュールの特異メソッドでもあるようなメソッドです。 例えば Math モジュールのメソッドはすべてモジュール関数です。

class Module - Ruby 2.2.0 リファレンスマニュアル

module M1
  def foo
    'foo'
  end
  module_function :foo
end

class Foo
  include M1
end

puts M1.foo
#=> foo

# fooはプライベートになる
# puts Foo.new.foo
#=> private method `foo' called for #<Foo:0x007f844b087998> (NoMethodError)

まとめ

当面はこれだけの知識があれば大丈夫そう。わからないことがあったら都度調べればいいや。

参考