今はinjectよりもtapだよね〜

tapかわいいよtap - http://rubikitch.com/に移転しました

俺も含めたinject厨は、Ruby 1.8.7からtapへ鞍替えしたほうがいいかもしれない。

たとえば、簡単な例題として、1〜6のうちで偶数の二乗を集めるコードを示そう。

(1..6).select{|x| x % 2 == 0}.map{|x| x*x}          # => [4, 16, 36]
(1..6).inject([]){|a,x| a << x*x if x % 2 == 0; a}  # => [4, 16, 36]
[].tap{|a| (1..6).each{|x| a << x*x if x % 2 == 0}} # => [4, 16, 36]

一番上がselectとmapを使った関数プログラミング的な方法。短くて明確だけど強いていえば余計なオブジェクトが生成される上、ループが2回になる欠点がある。

二番目がinjectによる方法。なんか「; a」が気に食わない。

そして最後がObject#tapによる方法。injectよりも1文字長くはなるが、injectよりもeachの方が圧倒的に理解しやすいコードになる。しかも「; a」という余計なものがつかないのですっきりする。しかも速い。

ベンチマークをば。

require 'benchmark'
Benchmark.bmbm(10) do |b|
  max = 100000
  b.report("select") { (1..max).select{|x| x % 2 == 0}.map{|x| x*x} }
  b.report("inject") { (1..max).inject([]){|a,x| a << x*x if x % 2 == 0; a} }
  b.report("tap") { [].tap{|a| (1..max).each{|x| a << x*x if x % 2 == 0}} }
end
# >> Rehearsal ---------------------------------------------
# >> select      0.110000   0.000000   0.110000 (  0.115173)
# >> inject      0.110000   0.000000   0.110000 (  0.105988)
# >> tap         0.100000   0.000000   0.100000 (  0.100107)
# >> ------------------------------------ total: 0.320000sec
# >> 
# >>                 user     system      total        real
# >> select      0.100000   0.000000   0.100000 (  0.106372)
# >> inject      0.090000   0.000000   0.090000 (  0.092665)
# >> tap         0.070000   0.000000   0.070000 (  0.069444)

ArrayやHashを破壊的更新する場合は、これからはtapだよね〜

おまけ:合計のイディオムは「inject(:+)」

injectがかわいそうなので擁護もしてみる。
Ruby 1.8.7のinjectはSymbolを引数に取ることができる。つまり、合計は「inject{|s,x| s+x}」から「inject(:+)」でよくなった。

1+2+3+4                         # => 10
(1..4).inject(:+)               # => 10

1*2*3*4                         # => 24
(1..4).inject(:*)               # => 24

やっぱinjectもかわいいぜ。

追記

「inject(:+)」はRuby 1.8.6だとエラーになるよ。Ruby 1.8.7はそろそろ出るだろう。
まだ出てもいないRuby 1.8.7についてなんでわかるかって?リリース前のバージョンを使ってるから。
あと、Ruby 1.9は開発版なので初心者お断りwまだRuby 1.9についての解説書がほとんど出ていないしね。