変数と関数の名前空間

IIJ技術研究所 山本和彦
作成:2002/08/30
更新:2002/09/02

Lisp は「S式」だという説明をよく聞く。 でも、今どき何がS式なのか分っている人は少ない。 そもそも、「M式」と区別するためにS式という言葉が用いられたのだから、 M式を口にする人がほとんどいない今、 わざわざS式という必要もないように思う。

括弧をたくさん使っていたり、 ポーランド記法を使っていることは、S 式の本質ではない。 S式とは、データと関数が同じように書ける表現形式のことである。 M式ではデータと関数に異なる表記方法が用いられていたから、 同じように表現できることは画期的だったのだ。

しかし、Lisp ではデータと関数が同じように表現できることは当たり前だから、 今の時代 S 式なんて言葉を使う必要もない。

データと関数が同じであるなら、データを関数として呼び出したくなる。 たとえば、以下のようにだ。

(set 'foo (lambda (x) (+ x 2)))
(foo 3)
→ Symbol's function definition is void: foo

しかし、Emacs Lisp では上記のように失敗してしまう。 Emacs Lisp に慣れていると、「そんなのは当たり前だ」と思うかもしれないが、 Scheme ならこれは成功する。

関数を呼び出す関数に funcall があるので、 これを使ってみると以下のような結果になる。

(funcall 'foo 3)
→ Symbol's function definition is void: foo
(funcall foo 3)
→ 5

前者では、foo というシンボルには関数が束縛されてないことが分る。 後者は単純に foo という変数を評価して戻ってきた (lambda (x) (+ x 2)) を関数として呼び出しているだけだ。

set/setq の代りに、fset を使ってシンボルに対する関数を定義すると、 foo が関数として呼び出せるようになる。

(fset 'foo (lambda (x) (+ x 2)))
(foo 3)
→ 5
(funcall 'foo 3)
→ 5

なお、fset を使っても変数 foo の値は壊れない。

これらの実験から分るのは、 Emacs Lisp では変数と関数の名前空間が分れていることである。 こう書くと、変数用のシンボル表と、 関数用のシンボル表を別々に保持するよう実装されているように思うが、 そうではない。 lisp.h には、以下のような定義がある。

struct Lisp_Symbol
  {
    struct Lisp_String *name;
    Lisp_Object value;
    Lisp_Object function;
    Lisp_Object plist;
    Lisp_Object obarray;
    struct Lisp_Symbol *next;   /* -> next symbol in this obarray bucket */
  };

つまりシンボルは、変数の値と関数の実体へ別々にポインタを持っているのである。 set/setq は value を、fset は function を定義していることになる。 set/setq でいくら lambda をシンボルに束縛しても、 シンボルを関数として扱う場合は、それが利用されることはないわけだ。

ちなみに、boundp は変数が束縛されているか、 fboundp は関数が束縛されているかを調べる関数である。

Lisp_Symbol 構造体のメンバーの内、 上の 4 つは Emacs Lisp の関数で取り出すことが可能だ。 それぞれを以下に示す。

Lisp_Symbol 構造体の中で謎なのは、plist と obarray だ。

まず plist から解説しよう。 plist は property list の略語であることから分るように、 「属性」を保持するためのリストである。 変数のドキュメントはここに保存される。 次の例を見て欲しい。

(symbol-plist 'lpr-command)
→ (variable-documentation -764810)

lpr-command には、variable-documentation という属性があり、 その値は -764810 となっている。 これはドキュメントがどこにあるかを示す数値であり、 人が理解することは難しい。 この数値が示すドキュメントを取り出すには、以下のようにすればよい。

(documentation-property 'lpr-command 'variable-documentation)

では、関数のドキュメントについてはどうだろう? function-documentation という属性もあるようだが、 これが使われているところを私は見たことがない。 実際、関数の定義の中に関数のドキュメントは含まれているのであるから、 属性を使う必要はない。 シンボルが示す関数のドキュメントを取り出すには、 以下のようにすればよい。

(documentation 'help)

さて、obarray について解説しよう。 Emacs Lisp で obarray といえば、シンボルの配列のことである。 シンボル名からシンボルの実体に高速にアクセスするためのシンボル表だと考えてよい。 なぜ、obarray と呼ばれるのかは未だに謎である。 (知っている人は教えて欲しい! おそらく、object array の略だろう。)

Lisp_Symbol 構造体の obarray メンバーは、 自分が登録されているシンボル表を指す。

普通に defvar、set/setq、defun、fset を使えば、 シンボルはデフォルトのシンボル表に自動的に登録される。 他のシンボル表を利用する方法は、 「ハッシュ」のページで解説する。