Common Lispにおけるシンボルの多重名前空間

シンボルの多重名前空間

Common Lispでは、シンボルが他のプログラミング言語にはない独特の役割を果たしています。

シンボルは他の言語における「識別子」として機能し、変数や関数の名前付けに使用されます。同時に、シンボル自体が数値や文字列と同様に独立したデータ構造(オブジェクト)として存在します。例えば、Tnilは特別なシンボルであり、それ自身を値として持ちます。コロンで始まるすべてのキーワードシンボルは自己評価的です。

シンボルの実態はより複雑です。シンボルはメモリ内のデータを指す「参照」であると同時に、複数のスロットを持ち、文脈に応じて異なるスロットにアクセスします。

  • 値スロット:通常の変数としての値を格納
  • 関数スロット:関数定義を格納

同じシンボルが変数と関数の両方として機能する場合、インタプリタ(コンパイラ)はどのスロットにアクセスすべきかをどのように判断するのでしょうか?

シンボルが引数位置にある場合、システムは値スロットから値を取得します。シンボルが関数呼び出し位置(括弧の直後)にある場合、システムは関数呼び出しとして解釈し、関数スロットから関数を取得します。

実例による検証

;; 変数の暗黙的作成と32の代入
(setq sample-var 32)
=> 32

;; 値の確認
sample-var
=> 32

;; 関数sample-varの定義
(defun sample-var () 'nothing)
=> SAMPLE-VAR

;; 同一シンボルでの変数と関数の共存
sample-var
=> 32

(sample-var)
=> NOTHING

;; 値スロットに関数を格納
(setf sample-var #'(lambda () 'something))
=> #<FUNCTION (LAMBDA ()) {1002A45C8B}>

sample-var
=> #<FUNCTION (LAMBDA ()) {1002A45C8B}>

(sample-var)
=> NOTHING

(funcall sample-var)
=> SOMETHING

明示的な関数呼び出しでは関数スロットから関数が取得され、funcallを使用する場合、sample-varは引数位置にあるため値スロットから関数が取得されます。

(symbol-value 'sample-var)
=> #<FUNCTION (LAMBDA ()) {1002A45C8B}>

(symbol-function 'sample-var)
=> #<FUNCTION SAMPLE-VAR>

#'sample-var
=> #<FUNCTION SAMPLE-VVAR>

(function sample-var)
=> #<FUNCTION SAMPLE-VAR>

#'接頭辞はfunctionの糖衣構文であり、functionsymbol-functionの糖衣構文です。これらはシンボルの関数スロットから内容を取得します。

高階関数の適切な使用方法

関数を引数として渡す場合、#'を使用して関数本体を取得し、別の関数に渡す必要があります。受信関数内ではfuncallを使用してコードを直接実行できます。funcallに直接関数名を渡すと、システムはそれを通常の変数として扱い、値スロットから値を読み取ろうとしてエラーが発生します。

自己評価シンボル

(setf test-symbol 'test-symbol)
=> TEST-SYMBOL

test-symbol
=> TEST-SYMBOL

(symbol-value 'test-symbol)
=> TEST-SYMBOL

(symbol-value
  (symbol-value
    (symbol-value
      (symbol-value
       (symbol-value 'test-symbol)))))
=> TEST-SYMBOL

symbol-valueを何層ネストしても、自己評価シンボルは常にそれ自身を返します。これはシンボルが内部的に一意であり、同じシンボルはすべてメモリ内の同じアドレスを指すためです。

シンボルアクセサ

シンボルには最大5つのスロットがあります:

  • symbol-name:シンボルの文字列表現
  • symbol-value:具体的な値
  • symbol-function:関数定義
  • symbol-plist:各シンボルが自動的に持つプロパティリスト
  • symbol-package:シンボルが属するパッケージ
(symbol-name 'example)
=> "EXAMPLE"

;; 未宣言シンボルからのプロパティ値取得
(get 'crimson 'hue)
=> NIL

;; 直接プロパティ設定
(setf (get 'crimson 'hue) 'red)

;; プロパティ値の読み取り
(get 'crimson 'hue)
=> RED

Lispリーダーが新しいシンボルを読み取るたびに、メモリ内に新しいオブジェクトが自動的に作成されます。これにより、Common Lispの変数宣言は他の言語とは大きく異なる特徴を持っています。

二種類のスコープ

レキシカルスコープ

(declare (special var))として宣言されていないローカル変数はすべてレキシカル変数です。レキシカル変数のスコープは字句的範囲に制限されます。呼び出し時に、その値は新しい環境内の同名変数によって上書きされません。

ダイナミックスコープ

defparameterおよびdefvarで宣言されたグローバル変数はすべて「ダイナミックスコープ」を持ちます。ダイナミックスコープの変数は新しい環境内の同名変数によって上書きされます。setfまたはsetqで暗黙的に作成されたグローバル変数はダイナミックスコープを持ちません。

伝統的に、Lispプログラマーはグローバル変数の名前をアスタリスクで囲むことで、動的変数であることを強調します。

Common Lispのこの複雑さは歴史的な経緯によるものであり、SchemeはCommon Lispよりも純粋なLISPと言えますが、言語機能の面ではCommon Lispに劣る部分もあります。

タグ: Common Lisp シンボル 名前空間 レキシカルスコープ ダイナミックスコープ

5月19日 16:21 投稿