Lispの変数とシンボルは同じか?

目次

David S. Touretzkyの"COMMON LISP: A Gentle Introduction to Symbolic Computation"では,変数に値を代入するという言葉を使い,束縛という言葉を使いません.同書158ページには「シンボルは値と束縛されない.変数だけが値と束縛されうる(Symbols are never bound; only variables can be bound)」と,意味深なことを言っています.そこで,シンボル,変数,束縛について調べてみました.

まず,ANSI X3J13のFinal Draftによると,束縛は「名前」と「名前が指し示すもの」を結びつけることと記されています(an association between a name and that which the name denotes).

シンボルとは

X3J13にはシンボルは「an object of type symbol」と説明されています.シンボルは「シンボル型」という型のオブジェクトですね.シンボルは数値や文字列のようにLispの組み込み型(アトム)なので,数値や文字列と同じようにそれ自身がデータになります.

シンボルはコンテキスト(プログラム内の文脈)によってカメレオンのように姿を変えます.シンボル型のオブジェクトは「名前」と「値」と「関数」の3つのフィールドを持ちます.図1は前掲文献p.154にあるシンボル・オブジェクトの概念図です.「値」フィールドには別のアトムへの参照を保持することができ,「関数」フィールドには関数への参照を保持することができます.従って「名前」が表すデータとしてシンボルだけでなく,値や関数を格納しておく「変数」の役割も果たすのです.

symbol
図 1. シンボル・オブジェクトの概念図

図ではこのシンボル名は CAR でその値は ROLLS-ROYCE です.さらに関数フィールドには,CAR 関数への参照が設定されています.Lispで書くとこんな感じでしょうか.

(setf car 'ロールスロイス)   (1)

car                        (2)
;; => ロールスロイス

(symbol-value 'car)        (3)
;; => ロールスロイス

(symbol-function 'car)     (4)
;; => #<FUNCTION CAR>
1 carシンボルに「ロールスロイス」シンボルを代入.
2 carシンボルを評価してみる.
3 carシンボルの「値」属性を取り出す.
4 carシンボルの「関数」属性を取り出す.

SETF 関数を使って car シンボルに ロールスロイス というシンボルを代入したのだから,car シンボルは変数と同じじゃないか,と思いますよね.ここではその通りです.シンボルは変数に「名前付け」するのに使われます.しかし常に「変数=シンボル」ではありません.ローカル変数はシンボルではないからです.

変数とは

沢山の変数がプログラム中に作成され,それら変数の識別子として「名前」が付けられます.その名前に「シンボル」が使われることもありますが,必ずしもシンボルが使われるというわけでもありません.束縛されるのは「値とシンボル」ではなく,「値と名前」です.

実は,シンボルが変数の名前として使われるのは,大域変数を指し示す時だけです.シンボル型オブジェクトの「値」フィールドに格納できるのは「大域変数の値」のみと規格で定められています.ローカル変数(レキシカル変数)の値は規格上シンボルには設定できません.大域変数の場合はシンボルが変数の役割(値の格納場所)を果たすので,変数とシンボルの違いがわかりづらく,「変数=シンボル」と勘違いしてしまいますが,ローカル変数の場合はシンボルに束縛されません.

以下のコードではローカル変数(レキシカル変数)がシンボル型オブジェクトではないことを示しています.

(setf X "グローバル変数")
(defun foo ()
  (let ((X "局所変数"))
    (format t "X=~a, ~a~%" X (eql X 'X))))  (1)

(foo)
;; => X=局所変数, NIL
;;    NIL
1 局所変数 X とシンボル X は同じではないので,eqlNIL を返す.

シンボル型オブジェクトは名前の一意性から,同じパッケージ内であれば 'X は常に同じシンボル型オブジェクトを指すことが保証されています.したがって,シンボル 'X はグローバル変数 'X' を指します.let バインディングで作成された X は局所変数なので当然シンボル X とは別物です.

それでは,局所変数 X とは一体何者なのだ,と思いますが,これは調べる術がありません.なぜなら,let ブロック内で,

(class-of X)

と書けば class-of に渡される前に評価されて中身の文字列のクラスを調べることになってしまいますし,かといって,

(class-of 'X)

と書けば,これは一意性が保証されたグローバルのシンボル型オブジェクト X になり,もはや局所変数 X とは別物になってしまいます.同じ理由で局所変数に対して symbolpsymbol-value を使ってシンボルかどうか調べることはできません.サンクを使って局所変数の評価を遅らせてもダメでした.

まとめ

冒頭でTouretzky氏の「シンボルと値は束縛されない」という言葉に触れました.大域変数の場合はシンボルに値が格納されていますが,ANSIの規格書では「シンボルと値が束縛される」という言葉の使い方をしていません.全て「変数と値」か「名前と値」の束縛という書き方をしています.ということで,Touretzky氏は正しかったということになります.局所変数の正体は調べることができませんでしたが,少なくとも規格上,局所変数の値はシンボルに格納されません.つまり,シンボルと変数は同じではないということです.