Module#module_functionって必要なのかしら
ユーティリティ関数をモジュールに定義しておいて、名前空間を分けてグローバルに利用するというアプローチが良くある。Rubyでは、moduleを使ってメソッドをグローバル名前空間と切り分ける。例えばピュアRubyのWebサーバ「Webrick」には、webrick/httputils.rbというのがあって、
module WEBrick module HTMLUtils ## # Escapes &, ", > and < in +string+ def escape(string) str = string ? string.dup : "" str.gsub!(/&/n, '&') str.gsub!(/\"/n, '"') str.gsub!(/>/n, '>') str.gsub!(/</n, '<') str end module_function :escape end end
と書かれている。クライアント側では、
@body << <<-_end_of_html_ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"> <HTML> <HEAD><TITLE>#{HTMLUtils::escape(@reason_phrase)}</TITLE></HEAD> <BODY> <H1>#{HTMLUtils::escape(@reason_phrase)}</H1> #{HTMLUtils::escape(ex.message)} <HR>
というように使っている。これはいいんだけど、これと似たことをやる場合に、ぼくは、
module MyUtils def self.encrypt(str, options) lower = ("a".."z").to_a.rotate(options[:key]).join upper = lower.upcase str.tr('a-z', lower).tr('A-Z', upper) end end end puts MyUtils.encrypt("Hellow World!", key:3)
とやるのが正解かと思っていた。両者の違いは何だ? なぜModule#module_functionを使うんだろうか?
module MyUtils def self.meth puts "meth" end end module MyUtils2 def meth puts "meth" end module_function :meth end MyUtils::meth => "meth" MyUtils2::meth => "meth"
というように両者は、ほぼ同じに見える。class.cを見ると、
void rb_define_module_function(VALUE module, const char *name, VALUE (*func)(ANYARGS), int argc) { rb_define_private_method(module, name, func, argc); rb_define_singleton_method(module, name, func, argc); }
とある。つまり両者の違いは、Module#module_functionを使うと、一旦インスタンスメソッドとして定義したものを、プライベートにしつつ特異クラスにコピーを定義するというところ。違いがハッキリでるのは以下のような例。
module MyUtils def self.meth puts "meth" end end module MyUtils2 def meth puts "meth" end module_function :meth def meth puts "meh?" end end MyUtils::meth # => "meth" MyUtils2::meth # => "meth" class MyClass include MyUtils2 end MyClass.new.meth # => "meh?" MyUtils2::meth # => "meth"
しかし、分からんな。module_functionを使う場合でも、インスタンスメソッドなんて使わないだろうし、プライベートになるならほとんど使えない。いちいち「def self.meth」とselfを付ける記法が嫌だとしても、
module MyUtils class << self def meth puts "meth" end def meh puts "meh?" end end end MyUtils::meth # => "meth" MyUtils::meh # => "meh?"
とすればいいこと。うーん、好みの問題ってことかしら。ともあれ、モヤモヤしていたModule#module_functionのことが分かってスッキリした。