Smalltalk と Ruby と LISP のシンボル


SmalltalkRubyLISP もシンボルの本質は、いずれも“インターンされた文字列”です。インターン(intern)にはよい訳語を思いつかないのですが、簡単には「登録され管理された…」という感じでしょうか。しかし、実装や振る舞いは必ずしもそのとおりにはなっていません。特に文字列だと意識して扱う際には注意が必要でしょう。


注意:ここでの「Smalltalk」とは SqueakSmalltalk を指します。他の Smalltalk 処理系(特に、デファクト・スタンダードVisualWorksANSI 準拠のお手製 Smalltalk 処理系)では異なる挙動を示すとのご指摘を受けましたので、お詫びして修正、以下の項目に追記をさせていただきます。


Smalltalk のシンボル

Smalltalk のシンボルは、“等価なら常に同一で、イミュータブルな文字列”というふうに位置づけられ、実際に実装もそうなっています(シンボルのクラス Symbol は、String のサブクラスである、とか)。

等価なら同一
'hoge' copy =  'hoge' copy   " => true "
'hoge' copy == 'hoge' copy   " => false "
#hoge  copy =  #hoge  copy   " => true "
#hoge  copy == #hoge  copy   " => true "
イミュータブル
'hoge' at: 2 put: $a; yourself   " => 'hage' "
#hoge  at: 2 put: $a             " => Error: symbols can not be modified. "

なお、VisualWorksSmalltalk では、文字列でもリテラルはイミュータブルなので、いったんコピーしてからでないとエラーになります。

'hoge' at: 2 put: $a; yourself        " => Error "
'hoge' copy at: 2 put: $a; yourself   " => 'hage' "


これらの特徴に矛盾しない限り、文字列と同様に振る舞い、また同じ内容の文字列とは等価とみなされます。

文字列のように振る舞う
'hoge' at: 3        " => $g "
#hoge  at: 3        " => $g "
'hoge' asUppercase  " => 'HOGE' "
#hoge  asUppercase  " => 'HOGE' "
同じ内容の文字列とは等価とみなされる
'hoge' = #hoge    " => true "
#hoge  = 'hoge'   " => true "

追記:初期の Squeak(1.13)から 2.x までは、これとは異なる挙動でした。おそらく Smalltalk-80 では(Squeak 1.13 同様、)前者は true 後者は false だったのが、ANSI では双方 false に、ANSI とは距離をおいている Squeak では双方 true になった…という経緯があったようです。したがって「同じ内容なら等価とみなされる」かどうかは、Smalltalk でも意見が分かれるところのようです。

Ruby のシンボル

Ruby のシンボルは、等価なら同一という一意性のほうに重きを置いた位置づけになっています。文字列の代わりに用いることもできますが、必ずしも文字列と同じ振る舞いはしません(to_s により明示的に文字列に変換する必要があります)。「 Ruby ソースコード完全解説」の第2章「オブジェクト」によれば、Ruby のシンボルは、以前は単なる Fixnum(Smalltalk の a SmallInteger )だったそうですから、C の enum のような機構や役割をイメージした方が無難…というような印象を個人的には受けます。(追記:1.9 以降では、仕様が変更になる可能性が高いようです。)

等価なら同一
"hoge".id == "hoge".id   #=> false
:hoge.id  == :hoge.id    #=> true
文字列の代替えとして使えるときもあるが、文字列のようには振る舞わない
Object.method("to_s")  #=> #<Method: Class(Module)#to_s>
Object.method(:to_s)   #=> #<Method: Class(Module)#to_s>
"hoge"[2].chr   #=> "g" 
:hoge[2].chr    #=> NoMethodError
"hoge".upcase   #=> "HOGE"
:hoge.upcase    #=> NoMethodError
同じ内容でも文字列とは区別される
"hoge" == "hoge"   #=> true
"hoge" == :hoge    #=> nil     1.8.2 以降では false。
:hoge  == "hoge"   #=> false

LISP のシンボル(Common Lisp などの場合)

両者に影響を与えた LISP のシンボルは、それぞれのシンボルとはまた一線を画したものになっています。文字列のように見えても、必ずしも常にそのように扱えるとは限らない点に関しては、Ruby のシンボルによく似ています。しかし、その実体は整数のような単純なものではなく、対応する文字列はもちろん、インターンされたパッケージ名、値や関数(あるいは属性リスト)というように多岐にわたる情報を保持できる比較的複雑なオブジェクトであるようです(参考:シンボルとパッケージ)。

等価なら同一
(eq "hoge" "hoge")   ;;=> NIL
(eq 'hoge  'hoge )   ;;=> T
必ずしも常に文字列のように扱えるわけではない(関数の実装に依存?)
(char "hoge" 2)            ;;=> #\g
(char 'hoge  2)            ;;=> argument HOGE is not STRING
(string-upcase "hoge")     ;;=> "HOGE"
(string-upcase 'hoge )     ;;=> "HOGE"

ただし、

(symbol-name 'hoge)        ;;=> "HOGE"
(symbol-name '|hoge|)      ;;=> "hoge"
(string-upcase '|hoge| )   ;;=> "HOGE"
同じ内容の文字列とは区別される
(equal "HOGE" 'hoge)       ;;=> NIL
(equal "hoge" '|hoge|)     ;;=> NIL

ただし、文字列と見なすときは(当然だが…)等価と判断される。

(string= "HOGE" 'hoge)     ;;=> T
(string= "hoge" '|hoge|)   ;;=> T
値 and/or 関数を束縛して、それぞれの別名として使える
(setq foo 4)
(defun foo (x) (+ 3 x))
(symbol-name 'foo)      ;;=> "FOO"
(symbol-package 'foo)   ;;=> #<PACKAGE COMMON-LISP-USER>
(symbol-value 'foo)     ;;=> 4
(symbol-function 'foo)
   ;;=> #<CLOSURE FOO (X) (DECLARE (SYSTEM::IN-DEFUN FOO)) (BLOCK FOO (+ 3 X))>
(* 3 foo)   ;;=> 12
(foo 0.5)   ;;=> 3.5
(foo foo)   ;;=> 7