読者です 読者をやめる 読者になる 読者になる

rubyのHashの挙動が謎な件

ruby

ruby本(ピッケル本)を読み進めてたら、こんな感じのコードがありました。

hash = {}
hash['hoge'] = [] if hash['hoge'].nil?
hash['hoge'].push('a')

これを見て、2行目冗長じゃね?とruby歴4日のベテランであるところの僕は思いました。
初期値決めときゃええやん。

irb(main):001:0> hash = Hash.new([])
=> {}
irb(main):002:0> hash['hoge'].push('a')
=> ["a"]

が!

irb(main):003:0> hash
=> {}

空やんけ!さらに

irb(main):004:0> hash.keys
=> []
irb(main):005:0> hash['hoge']
=> ["a"]

このhogeはどちらのhoge?? <<追記:これはhogeキーのvalueが見えてるわけではなく、hogeが未定義な為、hash.defaultが見えてるだけ。
ちなみにこれは当然OK。

irb(main):006:0* hash = Hash.new([])
=> {}
irb(main):007:0> hash['hoge'] = ['a']
=> ["a"]
irb(main):008:0> hash
=> {"hoge"=>["a"]}
irb(main):009:0> hash['hoge'].push('b')
=> ["a", "b"]
irb(main):010:0> hash
=> {"hoge"=>["a", "b"]}
irb(main):011:0>

もうさっぱり意味がわかりません。
唯一のruby仲間でruby先輩のお兄さんに聞いてみましたが、一緒に唸るだけでした。
しょうがない。このブログなんてだーれも見てないだろうから、今度メーリングリストで聞いてみよう。

追記

コメントで教えていただきました。ブログってすげー。
http://www.ruby-lang.org/ja/man/html/trap_Hash.html
'<<'とかpushとか破壊的メソッドを使うと、コンストラクタで指定したデフォルトオブジェクトが書き換わるだけみたい。
こういうときは += なりで非破壊な再代入をすべしと。

irb(main):034:0* hash = Hash.new([])
=> {}
irb(main):035:0> hash['hoge'] += ['a']
=> ["a"]
irb(main):036:0> hash
=> {"hoge"=>["a"]}
irb(main):037:0> hash['foo'].push('b')
=> ["b"]
irb(main):038:0> hash
=> {"hoge"=>["a"]}
irb(main):039:0> hash.default
=> ["b"]
irb(main):040:0> hash['baz'] += ['c']
=> ["b", "c"]
irb(main):041:0> hash
=> {"baz"=>["b", "c"], "hoge"=>["a"]}
irb(main):042:0>

一回破壊してしまうと、以降のvalueにも影響を与えるので気をつけないといけないな。
上の例はこうすればいいのね。

irb(main):041:0* hash = Hash.new
=> {}
irb(main):042:0> (hash['hoge'] || []).push("a")
=> ["a"]

デフォルトオブジェクトは、あくまで未定義のとき参照する値として使えと。初期値ではない。