__send__メソッド

以前、デジカメ写真の取り込みスクリプトで、ファイルをcpするかmvするかをオプションで切り替える処理を以下のように書いた。

if Options[:copy] || Options[:move]
  f = Proc.new {|src,dest,opt|
    case
    when Options[:copy]
      FileUtils.cp(src,dest,opt)
    when Options[:move]
      FileUtils.mv(src,dest,opt)
    end
  }
  Options[:events].each {|n|
    nikon.event(n).each {|photo|
      f.call(photo[0],".",
             {:verbose => Options[:verbose]})
    }
  }
else
(省略)

条件別にProcオブジェクトを作って、それを実行するという形にしていたけど、どうももっと簡単な方法があったようだ。Rubyではメソッドを呼び出すときに、Object#sendというのが使えるらしい。sendは名前が衝突して上書きされる可能性があるので、実際には別名の__send__というのを使って、「object.__send__(:method,*args)」というように書く。アンダースコアが並びまくる名前って、だいたいおっかないもののような気もする。

method名は文字列かシンボル。

>> 1.__send__(:+, 2)
=> 3
>> 1.__send__("+", 2)
=> 3
(calc.rb)
puts ARGV.shift.to_i.__send__(ARGV.shift, ARGV.shift.to_i)

$ ruby calc.rb 2 + 5
7
$ ruby calc.rb 2 "*" 5
10

呼び出されるメソッドが実行時に決まるって、何か楽しげだ。もしかして、こういうところが「動的」というのだろうか。ともあれ、写真取り込みスクリプトの例では、何も大げさにProcオブジェクトなんか作らなくても、

if Options[:copy] || Options[:move]
  act = case
           when Options[:copy] then :cp
           when Options[:move] then :mv
           end
  Options[:events].each {|n|
    nikon.event(n).each {|photo|
      FileUtils.__send__(act,photo[0],".",
                         {:verbose => Options[:verbose]})
    }
  }
else
(省略)

という風にまとめられる。たぶん。「たぶん」、というのは、カメラを買い換えたら、このスクリプトが不要になってしまって、それきりだから……。