Rubyではなぜmix-inでクラスメソッドを引き継げないのか
includeと継承の違い
include
メソッドによりモジュールをinclude(Mix-in)してそのモジュールのインスタンスメソッドを使えるようになります。クラスメソッドは使えません。
instance method Module#include (Ruby 2.4.0)
一方、継承では親となるクラスのインスタンスメソッド・クラスメソッドの両方を使えるようになります。
module Foo def foo_instance_method "foo_instance_method" end def self.foo_class_method "foo_class_method" end end class SuperBoo def boo_instance_method "boo_instance_method" end def self.boo_class_method "boo_class_method" end end class Boo < SuperBoo include Foo end boo = Boo.new boo.boo_instance_method #=> "boo_instance_method" boo.foo_instance_method #=> "foo_instance_method" Boo.boo_class_method #=> "boo_class_method" Boo.foo_class_method #=> NoMethodError: undefined method `foo_class_method' for Boo:Class
上記でBoo
クラスがFoo
モジュールをincludeしたことでBoo
クラスの継承チェーンにFoo
モジュールが組み込まれます。
Boo.ancestors #=> [Boo, Foo, SuperBoo, Object, PP::ObjectMixin, Kernel, BasicObject]
ここだけ見ると、includeは継承の一種かなと思ってしまいます。違いが出るのは特異クラスの継承チェーンを見たときです。
Boo.singleton_class.ancestors #=> [#<Class:Boo>, #<Class:SuperBoo>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, PP::ObjectMixin, Kernel, BasicObject]
上記で確認できるように、includeしたモジュールは特異クラスの継承チェーンには出てきません。このため、includeしたモジュールのクラスメソッドを使用できません。つまり、includeと継承は一見似ているようですが全く異なる処理となります。
includeしたモジュールのクラスメソッドも使いたい
上記の通り、includeしたモジュールからはクラスメソッドを引き継げません。このため、ある責務を持ったモジュールのクラスメソッドを複数のクラスへ引き継ぎたいという場合にどうするかという問題が出てきます。この場合はincludedを使った実装をするのが一般的です。
module Foo def foo_instance_method "foo_instance_method" end def self.included(base) base.extend(ClassMethods) end module ClassMethods def foo_class_method "foo_class_method" end end end class SuperBoo def boo_instance_method "boo_instance_method" end def self.boo_class_method "boo_class_method" end end class Boo < SuperBoo include Foo end Boo.foo_class_method #=> "foo_class_method"
上記の実装はRailsでも使用されています。
なぜincludeでクラスメソッドを継承しないのか
そもそもなぜincludedを使用するようなめんどくさいことをしないとincludeしたモジュールのクラスメソッドを引き継げないのでしょうか。
上記で述べたようにincludeでクラスメソッドを引き継ぐ、という要望は普通にあるみたいです。であれば、Rubyの仕様としてincludeしたモジュールのクラスメソッドも引き継ぐというのはありだったんじゃないか、ということを考えました。その方がincludedを使用せずともクラスメソッドが使えるようになるから楽ですよね。
多重継承と同じになってしまうから、というのも考えましたがまあこれは違うかと。複数のモジュールをincludeしても継承しても、継承チェーンの中での順番は確定するからです。
とはいえ、includeでクラスメソッドを継承しないという選択をしているからには何かしらのメリットがあるということです。このメリットが何か私にはわからなかったので調べてみることにしました。
includeが使用されるそもそもの想定とは
Ruby forumの過去のスレッドで同じようなことを疑問に思った人がスレッドを立てていました。
Why the lack of mixing-in support for Class methods? - Ruby Forum
疑問に対するmatzさんの回答もありました。
https://www.ruby-forum.com/topic/68638
引用します。
Mix-in is used for several purposes and some of them can be hindered by inheriting class methods, for example, I don’t want to spill internal methods when including the Math module. I am not against for some kind of inclusion that inherits class methods as well, but it should be separated from the current #include behavior.
以下、matzさんの考えへの私の解釈です。
Mix-inが使用される想定(例えば、後述するMathモジュールみたいな使い方)がいくつかあるんだけど、クラスメソッドを継承したらそれがうまくいかない。 例えば、Mathモジュールを継承するときにクラスメソッドまで継承したら、includeしたクラスでメソッド名(名前空間)がかなり侵されてしまうのを避けたい。Mathモジュールの使い方を見ると、インスタンスメソッドとクラスメソッドにおそらく同名のメソッドを持っています。MathモジュールをincludeすればMathモジュールのクラスメソッドは不要になりますが、この時クラスメソッドも継承していると不要になったクラスメソッドの分だけincludeしたクラスでメソッド名の衝突が起きてしまう恐れがあります。