Rubyノート - include すると関数的に呼び出せるモジュールメソッドの仕組み

モジュールにインスタンスメソッドとして定義された機能をモジュールメソッドとして使うには、Module#module_function によってインスタンスメソッドの定義本体を同じ名前のモジュールメソッドとしてコピーする。

# Sample code from Programing Ruby, page 340
module Math
  def sin(x)
    #
  end
  module_function :sin
end

Math.sin(1)
include Math
sin(1)

トップレベルに include されている Kernelモジュールのメソッド(gets, puts, system など)が、(ピッケル本ライブラリ編によれば)モジュールメソッド(クラスメソッド相当)とされているにも関わらず関数的*1に呼び出し可能なのは、これらのメソッドが(self を参照しないにも関わらず)インスタンスメソッドとして定義されており、その上で同名のモジュールメソッドをそのコピーとして持っているからで、include することによって関数的に呼び出せるようになるのはインスタンスメソッドの方だ。

同様に FileUtils の各メソッド(cd, cp, mv, rm など)も include したら関数的に呼び出せるようになるが、これも理屈は同じで、ソースを覗いてみるとちゃんとインスタンスメソッドとして定義された上に module_function されている。

  ↓

# /usr/lib/ruby/1.8 /fileutils.rb

  #
  # Options: verbose
  # 
  # Changes the current directory to the directory +dir+.
  # 
  # If this method is called with block, resumes to the old
  # working directory after the block execution finished.
  # 
  #   FileUtils.cd('/', :verbose => true)   # chdir and report it
  # 
  def cd(dir, options = {}, &block) # :yield: dir
    fu_check_options options, OPT_TABLE['cd']
    fu_output_message "cd #{dir}" if options[:verbose]
    Dir.chdir(dir, &block)
    fu_output_message 'cd -' if options[:verbose] and block
  end
  module_function :cd

クラス定義であれば本来クラスメソッドにすべきところを、あえてインスタンスメソッドとして定義した上で module_function しておくと、Mix-in したとき

(゚д゚)ウマー

*1:インスタンスメソッドとして、self を省略した形で、ぐらいの意味。