シリーズ・RubyのSymbol 。だいぶ空いてしまったけど、気が向いたので続きを書く。
前回 は文字列の同一性について復習したのであった。
意義
そう言うわけで、文字列においては同一性と同値性は異なる。特に、Rubyの場合は同じリテラルも評価する度に異なるオブジェクトを生成する。ところが、これは不便な場合があるわけだ。
まず、文字列の同値性比較はコストがかかる。長い文字列を比較にするには時間が掛かる。保持しておくにも長い文字列だったらメモリーを食う。更に、毎回リテラルからオブジェクトを生成するとしたら、無駄なケースが多々ある。Railsに良く出てくる疑似名前付き引数みたいなやつ。
some_method "a" => 1, "b" => 2, "c" => 3
おい、この箇所を通る度に一々文字列のメモリー確保してオブジェクト構築するのか? その背後ではパフォーマンス厨なら誰もが恐れる malloc(3)
が動いているというのに?
ま、文字列本体についてはRuby処理系もそんなに馬鹿じゃなくて、実際にはもう少し効率の良い処理をする(のだったと思う。確か)。でも内部でRString構造体の確保が毎回必要になるのは本当だ。
とにかく、文字列が含んでいる「文字の列」部分を確保して保持しておくのは色々大変だ。最小限に抑えたい。そこで、internする。
intern操作
intern操作によって、文字列はオブジェクトとして一意になる。だから、同一性判定するだけで低コストで比較できる。
10.times do p "foo".object_id end
すると毎回違う結果が出るが、
10.times do p "foo".intern.object_id end
は同じ結果がずらりと並ぶ。この「文字列をinternした結果」こそが Symbol
オブジェクトなのである。
internというのは別にRuby固有の話ではない。Javaにだって、 String#intern( がある。返ってくるのも String
オブジェクトであるっていう点がRubyと違うけれども。でも、「同値性の判定を同一性で済ませたい」というニーズは言語に依らずにある話。
そう言う感じで、Javaにとっては「文字列をinternしたもの」は文字列そのものだ。前回、文字列リテラルの同一性比較が true
だったのも、Javaの場合は文字列リテラルはあらかじめinternされているからだ。
そう言う意味で、 Symbol
は「internされた文字列」に限りなく近い。違うのは、同一性と同値性が同義であることと、変更不能であることぐらいだ。変更可能だったら同一性と同値性の一致が知らない間に崩れていたりして困るからね。
それに、Rubyにしたってしばらく前に1.9では Symbol
は String
のサブクラスだった。Matzがなんか、実装しちゃったのだ。私は大反対の立場で、まぁ、諸々の反対とバグが沢山出たことで取りやめになったけど。
メモリー
同じ内容のシンボルは常にオブジェクトとして同一である。従って、
10.times do :foo end
これは新たなオブジェクトを生成したりしない。詳しいことは次回扱うけれども、 :foo
みたいなシンボルリテラルの実体はRubyがソースコードを解析した時点で解決される。実行時にはメモリーの確保を伴わない。だから、速いしメモリーを食わない。
何故Railsがシンボルをいっぱい使っているのか? 1つの答えはこれだろう。
シンボルとは
シンボルとは何か。答えの1つはこれだ。
変更できない「文字列のようなもの」
- 変更のためのメソッドを持っていない
- 同値ならば、必ず同一である