RubyのSymbolと文字列の違いを研究室の輪講用に書いたのですが,折角なので公開したいと思います.
元々学部生に対する輪講用に書いた物なので,若干上から目線ですがご了承ください.
文字列とSymbolはよく似ています.プログラマから見ればどちらも文字列です.違いを一言で説明すると『プログラムが扱う』文字列か,『プログラマが見る』文字列かの違いです.例えば変数名は『文字列』ですが,rubyのオブジェクトである『文字列』ではないですよね?
例えば,今あなたがC言語でお絵描きライブラリを作っているとしましょう.その中に,色で塗りつぶすfillという関数があり,色を青・赤・緑の3色から選べ,fillの引数でそれを指定できるとしましょう.
fillの引数の設定方法として一番単純なのが,0を青,1を赤,2を緑として,0〜2で選択させる方法でしょう.しかし,それでは,どの数値が何色か覚えないといけないし,fill関数を知らない人から見れば,どういう意味の引数かすらさっぱり分かりません.
ではどうするかと言えば,普通はBLUE = 0, RED = 1, GREEN = 2と適当に定数を設定して,その定数を引数で指定させますよね.
こうして,fill関数の引数が意味の無い数値から意味のある文字列に変わったことによってプログラムが分かりやすい物となります.さて,ここで注意してほしいのは,ここで言う文字列はプログラムが扱うオブジェクトとしての文字列では無いということです.fill関数の引数として,"BLUE","RED", "GREEN"などとC言語の文字列を渡すということは普通しませんよね.それは,ここで言う文字列は,あくまでプログラマがプログラムコードを分かりやすくするために必要な文字列であって,プログラムがオブジェクトとして扱う(例えば,長さを求めるとかする)文字列ではないからです.
分かってきたでしょうか?
プログラムコード上では(つまりプログラマから見れば)どちらも同じ文字列(文字の列という意味で)ですが,実際に動くプログラムから見れば単なる数値と本物の文字列という大きな違いです.結局,fill関数の引数の具体的な値は何でもいいわけです.プログラマから見て文字列であればそれだけでよく,プログラムが動くときの実際のその中身は何でもいいわけです.これのために存在するのが,Symbolであり,:fooとひとたびSymbolを作成すれば:fooの実態は適当な数値となります.(この数値がいくらかなんていうことはもちろん気にする必要はありません)
そして,もちろん同じプログラム上では:foo == :fooはちゃんと成り立ちます.もうここまでくれば,Hashのkeyとして文字列でなくSymbolを使う理由が分かりますね.Hashのkeyはあくまで,プログラマが見る(プログラムコードを分かりやすくするための)文字列であってプログラムが扱うオブジェクトとしての文字列では無くて,keyの実際の値は何でもいい,からですね.(特別な場合を除いて)Hashのkeyに対してrubyのStringのメソッドを使うなんてことは無いですよね.
しかし,他の軽量言語ではSymbolなどなくHashのkeyとして普通に文字列を使うことが多いです.では,なぜrubyだけSymbolを使うのでしょうか.
その答えは一言でいうと,rubyの(プログラムコード上に直接書かれた,つまりリテラルの)文字列は他の言語と違いimmutable(不変)でない,からです.実際,pythonやjavascriptの文字列(リテラル)は破壊的に変更することはできませんが,rubyの文字列は破壊的に変更することができます. ('abc'.concat('d')の様に)
これがどういう違いを生むかというと,コード上に直接現れる文字列がimmutable(不変)であるならば,実行時に一つだけそのオブジェクトを作成し,後はそれを使いまわすという最適化ができます.
そうした時,Hashのkeyの様なプログラマから見た文字列というのは,プログラムコード上のリテラルとして現れるわけですが,これらは実行時に一つだけオブジェクトが作成され(特にコード上に現れる同じ文字列は全て一つのオブジェクトにまとめると),それらの比較はそれらに対する参照(そしてこれは大抵メモリのアドレスなど単なる数値)の比較で済むので,結局Symbolと同じ様な働きをするわけです.
本当はプログラマが見るためだけの文字列だけど,それをオブジェクトとしての文字列としても,Symbolと同じ様な働き,パフォーマンスが得られるならば,別にオブジェクトとしての文字列であってもいいわけです.
繰り返しになりますが,プログラマが見るためだけの文字列は,その中身・実態は何でもいいわけですが,その実態がオブジェクトとしての文字列でも十分なパフォーマンスが得られるならば,別にオブジェクトとしての文字列でもいいわけです.
さて,rubyに話を戻しますと,rubyはコード上に現れる文字列であっても,実行時にそのコードを通る度に毎回新たな文字列オブジェクトを作成します.
(以下のプログラムを動かすことで確認できる.)
def foo 'foo'.object_id end p foo, foo
つまり,rubyでは文字列が可変であるため,先に述べたような最適化ができない(または難しい)ので毎回新たな文字列オブジェクトが作成されるのです.
こうなると,先ほどの話とはうってかわって,プログラマが見る文字列はその実態は何でもいいのに,それを文字列リテラル(rubyのオブジェクトとしての文字列)にしてしまうと,毎回毎回文字列オブジェクトが作成されてしまうという非常にばかばかしい状況になってしまいます.我々はそれらの文字列オブジェクトに文字列としての操作は一切施さないのにも関わらず,です.
こういうわけで,rubyではプログラマが見るためだけの文字列にSymbolというruby特有のものを使うのです.
もちろん,プログラマが見るためだけの文字列を全て定数として(そしてもちろん中身は適当な値で)定義しても構わないわけですが,Hashのkeyとかで数多くのプログラマが見るためだけの文字列が現れることを考えると,とてもじゃないですけどそんなことは面倒でやってられないですよね.ですので,実行時に自動で適当な値にしてくれるSymbolというものが存在するのです.
以上で,Symbolについての説明を終えます.以下は蛇足です.
最初の方で出てきたfill関数をrubyで実装しようとしたとき,青・赤・緑の各色はその実際の値はなんでもいいのでrubyのSymbolを使って:blue, :red, :greenとしてもいいのですが,ライブラリとかでは大抵ちゃんと定数として定義されていることが多いです.
これは恐らく,定数として明示的に定義することで値の存在を明示でき,ドキュメント化の際にも役立つことによっているのでしょう.
しかし,あくまでこれは外部に公開するようなライブラリでの話であって,自分が使うちょっとしたプログラムならこういう場面でも精力的にSymbolを使っていってもいいと思います.ちなみに,僕ならSymbolを使います.
Symbolだと定義もいりませんし,定数は大文字ですから打つのが面倒ですし,あまりソースに大文字が入ると見た目がすっきりしません(主観).
Symbolは非常に便利なものですので,その意義・用途を十分に理解して,Hashのkeyにとどまらず様々な所で使えるようになりましょう.