Rubyのモジュール関数を理解しよう!
ブログを下記に移転しました。デザイン変更により移転先では記事が一層読みやすくなっていますので、よろしければ移動をお願い致します。
Rubyのモジュール関数を理解しよう! : melborne.github.com
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
RubyのMathモジュールには数学関数が定義されていて
それらは以下のようにモジュール・メソッドとして呼び出す使い方と
クラスにモジュールをインクルードして関数的に呼び出す使い方の
2種類の使い方ができるようになっています
Math.sqrt 4 # => 2.0 Math.atan2(1, 1) # => 0.785398163397448 include Math sqrt 4 # => 2.0 atan2(1, 1) # => 0.785398163397448
一方でこれらのメソッドはインクルードした場合
オブジェクトを指定するメソッド形式での呼び出しが
できないようにされています
Object.new.sqrt 4 # => private method `sqrt' called for #<Object:0x1a414> (NoMethodError)
このような形式で定義されたメソッドを
Rubyでは「モジュール関数」と呼んでいます
モジュール関数はその利用の態様に応じて使い方を選べるので
その利便性を高めます
早々自分でもモジュール関数redを備えた
Colorモジュールを定義してみます
module Color def self.red :red end private def red :red end end Color.red # => :red include Color red # => :red Object.new.red # => private method `red' called for #<Object:0x2272c> (NoMethodError)
Colorモジュールにredインスタンス・メソッドと
redモジュール・メソッドを定義し
インスタンス・メソッドの可視性をprivateにします
モジュール・メソッドは
Singletonクラスを使って定義してもいいですね
module Color class << self def red :red end end private def red :red end end Color.red # => :red include Color red # => :red Object.new.red # => private method `red' called for #<Object:0x226a0> (NoMethodError)
これで完了!
と言いたいところですが
明らかにこれらのコードには問題があります
そう DRY原則に反しているのです
同じコードの繰り返しはその保守性を下げるのでいけません
改善しましょう
singletonクラスにColorモジュールをインクルードすることによって
コードの重複を回避します
module Color class << self include Color end private def red :red end end Color.red # => private method `red' called for Color:Module (NoMethodError) include Color red # => :red Object.new.red # => private method `red' called for #<Object:0x230dc> (NoMethodError)
残念ながらredの可視性がprivateにされているので
Colorオブジェクトからredを呼び出せないようです
Singletonクラスへのインクルードはextendと等価ですから
extendも試してみます
module Color extend self private def red :red end end Color.red # => private method `red' called for Color:Module (NoMethodError) include Color red # => :red Object.new.red # => private method `red' called for #<Object:0x23190> (NoMethodError)
やはりダメです
さて...
苦肉の策を考えました
module Color class << self include Color def Red red end end private def red :red end end Color.Red # => :red include Color red # => :red Object.new.red # => private method `red' called for #<Object:0x225ec> (NoMethodError)
呼び出しの問題は解決しましたが
2つのメソッド名が異なるという致命的な問題が発生しました
あるいはsendを使って...
module Color extend self private def red :red end end Color.send :red # => :red include Color red # => :red Object.new.red # => private method `red' called for #<Object:0x227b8> (NoMethodError)
これではとてもモジュール関数とは呼べません
さてどうしたものでしょうか...
こうなったら最後の手段です
そう メタプログラミングです!
Colorモジュールにmod_funcというモジュール・メソッドを定義して
その引数としてインスタンス・メソッドを渡すと
それを自動でモジュール関数にしてくれるよう実装してみます
module Color def self.mod_func(meth) extend self private meth end def red :red end mod_func :red end Color.red # => private method `red' called for Color:Module (NoMethodError) include Color red # => :red Object.new.red # => private method `red' called for #<Object:0x22984> (NoMethodError)
最初の試みは失敗に終わりました
mod_func内のprivateでインスタンス・メソッドredだけでなく
モジュール・メソッドredもプライベート化されてしまうようです
今度はdefine_methodを使って
モジュール・メソッドredを別に定義してみます
module Color def self.mod_func(meth) extend self (class << self; self end).module_eval do alias_method :new_meth, meth define_method(meth) do |*args, &block| new_meth(*args, &block) end end private meth end def red :red end mod_func :red end Color.red # => :red include Color red # => :red Object.new.red # => private method `red' called for #<Object:0x21bec> (NoMethodError)
今度はうまくいきました!
mod_func内では以下のような処理が実行されます
- extendを使ってColorモジュールの抽象クラスのコンテキストで、redメソッドにアクセスできるようにする
- alias_methodにより、redメソッドをnew_methに別名定義する*1
- define_methodにより、インスタンス・メソッドと同じ内容のモジュール・メソッドredを定義する
- インスタンス・メソッドredをプライベートにする
mod_funcはモジュールにおいて汎用的に使えるので
これをColorモジュールだけの機能としておくのはもったいないです
Moduleクラスに移しましょう
class Module def mod_func(meth) extend self (class<<self;self end).module_eval do alias_method :new_meth, meth define_method(meth) do |*args, &block| new_meth(*args, &block) end end private meth end end module Color def red :red end mod_func :red end Color.red # => :red include Color red # => :red Object.new.red # => private method `red' called for #<Object:0x22150> (NoMethodError)
すてきです
ええ もちろん
Rubyはユーザにこんな手間を強いることはありません
Rubyにはモジュール関数を作るために
Module#module_functionというメソッドが用意されています
module Color def red :red end module_function :red end Color.red # => :red include Color red # => :red Object.new.red # => private method `red' called for #<Object:0x22240> (NoMethodError)
module_functionが引数を取らない場合
それ以降に定義されたメソッドがモジュール関数の対象になります
module Color module_function def red :red end end Color.red # => :red include Color red # => :red Object.new.red # => private method `red' called for #<Object:0x22830> (NoMethodError)
*1:aliasではうまくいかない