Enumerable#injectって分かりやすいのかな

CSVって、RFC4180とかに仕様がまとまってるんだとマイルドに驚きつつ、RubyCSVクラスをのぞいてみた。FasterCSVと呼ばれていたものが正式にRubyに入り、1.9系のRubyではインターフェースが変わっているという話。CSVライブラリのソースコードには、冗長といっていいほどコメントが豊富にあるし、Rubyでライブラリを実装するお手本のような感じもある気がする。つらつら読んでいて気になったのは、CSV::Row#to_hashにある、次のようなコード。

def to_hash
  # flatten just one level of the internal Array
  Hash[*@arr.inject(Array.new) { |ary, pair| ary.push(*pair) }]
end

# CSV.new([["a", 2], ["b", 3], ["c", 5]]).to_hash
#   => {"a"=>2, "b"=>3, "c"=>5}

もともとCSV::RowクラスがArrayとHashの両方の性質を備えているとはいえ、この節操のないArrayとHashの混ざり具合は、どうなんだろうか。スプラッタ演算子の濫用も気になる。というか、パッと見て意味が分からなかった。

これは、

def to_hash
  hash = { }
  @arr.each {|pair| hash[pair.first] = pair.last}
  hash
end

と書くといいんじゃないかしら。injectを使うにしても、injectすべきなのは、hashのほう。

def to_hash
  @arr.inject(Hash.new) {|h, pair| h[pair.first] = pair.last; h}
end

しかし、こうやって他人のソースコードのスタイルにケチを付けるのって、ケチな話だよなと思ったりもする。どうでもいいっちゃどうでもいいし、それより、役立つものを作れよという話なのかも。

そういえば、Hash["a", 2, "b", 3] => {"a"=>2, "b"=>3}というHashのコンストラクタも知らなかったけど、結構使われてるのだろうか。なんとなくRubyっぽくない。前から順番にハッシュ作るのか。引数の個数が偶数だとArgumentErrorで落ちる。

hash.cには、

static VALUE
rb_hash_s_create(int argc, VALUE *argv, VALUE klass)
{
    :
    :
    if (argc % 2 != 0) {
	rb_raise(rb_eArgError, "odd number of arguments for Hash");
    }

    hash = hash_alloc(klass);
    for (i=0; i<argc; i+=2) {
        rb_hash_aset(hash, argv[i], argv[i + 1]);
    }

とある。なるほど。

Hash[k1, v1, k2, v2...]というのがどのぐらい使わてるのか、Ruby本体で調べてみた。

find hoge/lib/ruby/1.9.1 -iname '*rb' |xargs grep 'Hash *\['

とやった結果は以下のとおり。スプラッタ演算子と相性は良さそう。しかし、ほとんどがtk関連かな。そしてよく見ると、resolv.rbは違うな。drb.rbの例もえぐいなあ。

.//csv.rb:      Hash[*@row.inject(Array.new) { |ary, pair| ary.push(*pair) }]
.//csv.rb:    meta = Hash[*csv.shift]
.//drb/drb.rb:      families = Hash[*infos.collect { |af, *_| af }.uniq.zip([]).flatten]
.//multi-tk.rb:      Hash[*_lst2ary(ip._eval("::safe::interpConfigure " +
.//psych/visitors/to_ruby.rb:          members = Hash[*o.children.map { |c| accept c }]
.//psych/visitors/to_ruby.rb:            h = Hash[*members]
.//psych/visitors/to_ruby.rb:          h = Hash[*o.children.map { |c| accept c }]
.//psych/visitors/to_ruby.rb:          h = Hash[*o.children.map { |c| accept c }]
.//psych/visitors/to_ruby.rb:          h = Hash[*o.children.map { |c| accept c }]
.//psych/visitors/to_ruby.rb:          h = Hash[*o.children.map { |c| accept c }]
.//psych/visitors/to_ruby.rb:        h = Hash[*node.children.map { |c| accept c }]
.//resolv.rb:        return ClassHash[[type_value, class_value]] ||
.//resolv.rb:          ClassHash[[type_value, class_value]] = c
.//resolv.rb:          ClassHash[[s::TypeValue, ClassValue]] = c
.//resolv.rb:          ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
.//resolv.rb:          ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
.//resolv.rb:          ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
.//resolv.rb:          ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
.//rubygems/specification.rb:  #   Hash[*spec.authors.zip(spec.emails).flatten]
.//rubygems/specification.rb:  #   Hash[*spec.authors.zip(spec.emails).flatten]
.//syck/types.rb:                        seq.add( Hash[ *v ] )
.//syck/types.rb:                        seq.add( Hash[ *v ] )
.//test/unit.rb:          @workers_hash = Hash[@workers.map {|w| [w.io,w] }] # out-IO => worker
.//tk/font.rb:    Hash[TkFont.actual(fnt, option)]
.//tk/font.rb:    Hash[TkFont.actual_displayof(fnt, option)]
.//tk/font.rb:    h = Hash[TkFont.metrics(fnt)]
.//tk/font.rb:    h = Hash[TkFont.metrics_displayof(fnt, win, option)]
.//tk/font.rb:              Hash[*tk_split_simplelist(tk_call('font', 'configure',
.//tk/font.rb:            Hash[*(tk_split_simplelist(tk_call('font', 'configure',
.//tk/font.rb:            Hash[*(tk_split_simplelist(tk_call('font', 'configure',
.//tk/font.rb:    Hash[actual(option)]
.//tk/font.rb:    Hash[actual_displayof(win, option)]
.//tk/font.rb:    Hash[latin_actual(option)]
.//tk/font.rb:    Hash[latin_actual_displayof(win, option)]
.//tk/font.rb:    Hash[kanji_actual(option)]
.//tk/font.rb:    Hash[kanji_actual_displayof(win, option)]
.//tk/font.rb:    Hash[latin_configinfo(slot)]
.//tk/font.rb:    Hash[kanji_configinfo(slot)]
.//tk/font.rb:    h = Hash[metrics(option)]
.//tk/font.rb:    h = Hash[metrics_displayof(win, option)]
.//tk/font.rb:    h = Hash[latin_metrics(option)]
.//tk/font.rb:    h = Hash[latin_metrics_displayof(win, option)]
.//tk/font.rb:    h = Hash[kanji_metrics(option)]
.//tk/font.rb:    h = Hash[kanji_metrics_displayof(win, option)]
.//tk/variable.rb:      #Hash[*tk_split_simplelist(INTERP._eval("global #{@id}; array get #{@id}"))]
.//tk/variable.rb:      Hash[*tk_split_simplelist(INTERP._invoke('array', 'get', @id))]
.//tk/variable.rb:        Hash[*tk_split_simplelist(INTERP._eval(Kernel.format('global %s; array get %s', @id, @id)))]
.//tk/variable.rb:        #Hash[*tk_split_simplelist(_fromUTF8(INTERP._invoke_without_enc('array', 'get', @id)))]
.//tk.rb:      Hash[*tk_split_simplelist(INTERP._invoke_without_enc('array', 'get',
.//tk.rb:      Hash[*tk_split_simplelist(INTERP._invoke('array', 'get', 'env'))]
.//tk.rb:      Hash[*tk_split_simplelist(INTERP._invoke('array', 'get', 'auto_index'))]
.//tk.rb:      Hash[*tk_split_simplelist(INTERP._invoke('array', 'get',
.//tkextlib/blt/container.rb:      Hash[*simplelist(tk_send_without_enc('find', '-command', pat))]
.//tkextlib/blt/container.rb:      Hash[*simplelist(tk_send_without_enc('find', '-name', pat))]
.//tkextlib/blt/tree.rb:      Hash[*simplelist(tk_call('::blt::tree', 'get', tagid(node)))]
.//tkextlib/blt/watch.rb:          Hash[*(info.flatten)]
.//tkextlib/blt/winop.rb:    Hash[*list(tk_call('::blt::winop', 'colormap', win))]
.//tkextlib/tile/style.rb:      Hash[*(simplelist(tk_call(TkCommandNames[0], 'map', style)))].each{|k, v|
.//tkextlib/tile/treeview.rb:          ret = Hash[*(tk_split_simplelist(tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)))), false, false))].to_a.collect{|conf|
.//tkextlib/tile/treeview.rb:          ret = Hash[*(tk_split_simplelist(tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)))), false, false))].to_a.collect{|conf|
.//tkextlib/tile.rb:        images = Hash[*TkComm.simplelist(Tk.tk_call(cmd, imgdir, pat))]
.//tkextlib/tile.rb:        images = Hash[*TkComm.simplelist(Tk.tk_call('array', 'get', 'images'))]
.//tkextlib/treectrl/tktreectrl.rb:          Hash[*(TkComm.list(val))].each{|k, v|
.//tkextlib/treectrl/tktreectrl.rb:    Hash[*list(tk_send('item', 'dump', item))]
.//tkextlib/treectrl/tktreectrl.rb:      Hash[*simplelist(tk_send('style', 'layout', style, elem))].each{|k, v|
.//tkextlib/winico/winico.rb:      h = Hash[*list(inf)]
.//uri/common.rb:  # p Hash[ary]            # => {"a"=>"2", "b"=>"3"}

Railsはどうかと思ったら、結構使われてる。中でごにょごにょとmapした結果からHashを作るというケースが多いらしい。

~/src/rails (master) find ./ -iname '*rb' |xargs grep 'Hash *\[' |wc -l
      73
~/src/rails (master) find ./ -iname '*rb' |xargs grep 'Hash.new' |wc -l
     111