Ruby はいつまで立っても知らない仕様・挙動に出会える味わい深い言語です。
今回は ruby 1.8 の Hash のハッシュ値が (内容物に関係なく) インスタンスだけに依存して決まることを知りました。知らなかったのかよ、というツッコミはなしで。
# 1.8 x = { :foo => :bar } y = { :foo => :bar } h = { x => 42 } p h[x] #=> 42 p h[y] #=> nil
ひどい落とし穴に見えますが、今まで実際に引っかかったことがなかったので、書きそうで書かないコードなんでしょう。Ruby のマニュアルの Hash の項目に「キーとして与えたオブジェクトの内容が変化し、メソッド hash の返す値が変わるとハッシュから値が取り出せなくなりますから、Array、 Hash などのインスタンスはキーに向きません。」とありますが、Hash のインスタンスは問題ないような気がします。
ただし、1.9 では内容物のハッシュ値から計算するようになっています。
# 1.9 x = { :foo => :bar } y = { :foo => :bar } h = { x => 42 } p h[x] #=> 42 p h[y] #=> 42
従って、1.9 では先ほどのマニュアルの通り、キーになっているハッシュを更新した場合に Hash#rehash が必要でした。
# 1.9 x = { :foo => :bar } h = { x => 42 } p h[x] #=> 42 x[:baz] = :qux # キーの更新 (ハッシュ値も変わる) p h[x] #=> nil h.rehash p h[x] #=> 42
ハッシュ自身をキーにすると不思議な気分になります。
# 1.9 h = {} h[h] = 1 p h[h] #=> nil
これも h[h] = 1 の実行時の h のハッシュ値と、p h[h] の実行時の h のハッシュ値が異なるためにこういうことになります。
# 1.9 h = {} h[h] = 1 h.rehash p h[h] #=> 1
h[h] の代入を複数回実行するとさらにカオスで、二回 rehash する必要があります。
# 1.9 h = {} h[h] = 1; p h #=> {{...}=>1} p h[h] #=> nil h[h] = 2; p h #=> {{...}=>1, {...}=>2} p h[h] #=> nil h[h] = 3; p h #=> {{...}=>1, {...}=>2, {...}=>3} p h[h] #=> nil h.rehash; p h #=> {{...}=>3} p h[h] #=> nil h.rehash; p h #=> {{...}=>3} p h[h] #=> 3
ついでに変なコード。
h1 = {} h2 = {} h1[h1] = 1 h2[h2] = 1 h1.rehash h2.rehash p h1 == h2 #=> 1.8 では false 、1.9 では SEGV
1.9 の SEGV は多分スタックあふれなので仕様っぽいですが、1.8 が false を返すのは謎です。true か stack level too deep を期待したのに。そういえば Ruby の等価性もちゃんとわかってないなあ。追記: SEGV しなくなったようです。