rubyを触っていると、何となく理解はしているが、シンボルと文字列の違いって何だっけ?とふと説明できないことに気がつきました。「シンボルはどこかに格納されているんだよなー」くらいの認識でした...
シンボルと文字列の違いは?
rubyではシンボルを使っても、文字列を使ってもいい感じに処理をしてくれます。したがって、間違った使い方だとしてもうまく処理されてしまうわけです。
しかし、内部的には実は違うことが行われていて、特に処理速度の違いが明確です。
-
文字列
「文字やバイトが並んだ列」で、テキストやバイナリデータの格納に向いている。 -
シンボル
「文字やバイトが並んだ列」が識別子として付与された数値
→シンボルはIDと呼ばれる整数値のデータ型を包むオブジェクトラッパーとして扱われる。
シンボルはRuby内部で一意のIDを持ち、その識別子に対応する数値を探索することで高速に処理を行うことができます。つまり、シンボルを作成すると、IDが付与されて、呼び出された時にその識別子(ID)を使って探しにいく訳ですね。
見た目は文字列なんですが、内部的には数値なんですね。
当然、コンピューターとしては、数値の方が処理が速いので、シンボルの方が処理が速くなります。
でも、上述の通り、rubyは文字列だとしてもいい感じに処理をしてくれます。しかし、裏を返せば、文字列が渡された場合、Rubyは文字列をIDに変換するという余計な仕事をしなければならない訳です。
したがって、適切に文字列とシンボルを使い分けないと、コンピューターに無駄な仕事をさせたり、力を発揮させられなかったりする訳です...
もう少し大切なポイントを具体的なコードとともに見ていきましょう。
シンボルはimmutableだ!
大切なのは「シンボルは書き換えられない」ということです。
以下のように文字列をdowncaseすると小文字に変換されますが、シンボルを小文字に変換しようとするとエラーが発生します。(破壊的ではない場合はもちろん普通に変換できます。)
要はIDが付与されているから、別のIDで書き換えるな!ということですね。
'HOGE'.downcase! #=> "hoge"
:HOGE.downcase! #=> NoMethodError
シンボルはRubyの内部で識別子として扱われており、値を変更することができません。
つまり、シンボルはimmutable(変更不可能)なオブジェクトであり、小文字に書き換えられません。
補足
余談ですが、downcase, upcaseなどのメソッドの戻り値は実はシンボルです。
内部の処理としては、シンボルに対応づけられた文字列に対する操作結果を再びシンボルに変換しています。
:HOGE.downcase
=> :hoge
これは以下と同義
:HOGE.to_s.downcase.to_sym
=> :hoge
シンボルは同一だ!
これが本当かもう少し見てみると、以下から正しいことがわかります。
'hoge' == 'hoge' #=> true
'hoge'.equal?('hoge') #=> false
:hoge == :hoge #=> true
:hoge.equal?(:hoge) #=> true
equal?メソッドは、オブジェクトが同一のオブジェクトであるかどうかを比較するメソッドです。つまり、2つのオブジェクトが同じオブジェクトである場合にのみtrueを返します。
文字列は見かけ上は同じですが、実は文字列 'hoge' を2回 "hoge" と書いているように見えますが、それぞれが別々のオブジェクトとしてメモリに割り当てられます。そのため、'hoge'.equal?('hoge') は false を返します。
一方で、シンボルの場合は識別子が付与されているため、同一になるという訳です。
どう使い分ければ良い?
で、色々とわかったのですが結局どう使い分けるか?が大切ですね。
まだ自信を持ってはいえないのですが、現段階では、
テキスト操作をするならば、文字列を渡す。それ以外はシンボル。
という認識で良いのかなと思います。
シンボルを受け付けられるのは、あくまでも内部で文字列に変換しているからに過ぎない訳です。ですので、コンピューターの処理を最大限にするために適切な方を選べるようになりたいです。また理解が深まったら修正しようと思います。
間違ったことを言っていたり不足があったらぜひコメントください!