不変キーは、ハッシュコードが安定しているため、一般的に意味があります。
これが、MRIコードのこの部分で文字列が特別に変換される理由です。
if (RHASH(hash)->ntbl->type == &identhash || rb_obj_class(key) != rb_cString) {
st_insert(RHASH(hash)->ntbl, key, val);
}
else {
st_insert2(RHASH(hash)->ntbl, key, val, copy_str_key);
}
一言で言えば、文字列キーの場合、st_insert2
重複とフリーズをトリガーする関数へのポインタが渡されます。
したがって、理論的には不変のリストと不変のハッシュをハッシュキーとしてサポートしたい場合は、そのコードを次のように変更できます。
VALUE key_klass;
key_klass = rb_obj_class(key);
if (key_klass == rb_cArray || key_klass == rb_cHash) {
st_insert2(RHASH(hash)->ntbl, key, val, freeze_obj);
}
else if (key_klass == rb_cString) {
st_insert2(RHASH(hash)->ntbl, key, val, copy_str_key);
}
else {
st_insert(RHASH(hash)->ntbl, key, val);
}
どこfreeze_obj
で定義されますか:
static st_data_t
freeze_obj(st_data_t obj)
{
return (st_data_t)rb_obj_freeze((VALUE) obj);
}
これにより、配列キーが変更可能であるという、観察した特定の不整合が解決されます。ただし、実際に一貫性を保つには、より多くの種類のオブジェクトも不変にする必要があります。
ただし、すべてのタイプではありません。たとえば、各整数値に対応するFixnumのインスタンスは事実上1つしかないため、Fixnumのような即時オブジェクトをフリーズする意味はありません。String
これが、この方法で特別な場合にのみ必要な理由であり、ではFixnum
ありませんSymbol
。
文字列はハッシュキーとして非常に頻繁に使用されるため、文字列は、Rubyプログラマーの便宜のために特別な例外です。
逆に、他のオブジェクトタイプがこのようにフリーズされない理由は、明らかに一貫性のない動作につながりますが、ほとんどの場合、Matz&Companyがエッジケースをサポートしないのは便利な問題です。実際には、配列やハッシュなどのコンテナオブジェクトをハッシュキーとして使用する人は比較的少数です。したがって、そうする場合は、挿入する前にフリーズするのはあなた次第です。
非即時オブジェクトをフリーズする動作は、すべてのオブジェクトに存在するビットフィールドのFL_FREEZE
ビットを反転するだけなので、これは厳密にはパフォーマンスに関するものではないことに注意してください。basic.flags
もちろん、これは安価な操作です。
また、パフォーマンスについて言えば、文字列キーを使用する予定で、コードのパフォーマンスクリティカルセクションにいる場合は、挿入を行う前に文字列をフリーズすることをお勧めします。そうしないと、dupがトリガーされますが、これはより高価な操作です。
Update @sawaは、配列キーを単純にフリーズしたままにしておくと、元の配列がキー使用コンテキストの外で予期せず不変になる可能性があることを指摘しました。これは、不快な驚きでもあります(ただし、配列をハッシュキー、本当に)。したがって、dup +freezeがそれを回避する方法であると推測した場合、実際には、顕著なパフォーマンスコストが発生する可能性があります。第三に、それを完全に凍結しないままにしておくと、OPの元の奇妙さを手に入れることができます。いたるところに奇妙さ。Matzらがこれらのエッジケースをプログラマーに委ねるもう1つの理由。