Common Lispの変数の種類と振舞い — #:g1

Posted 2010-08-14 14:35:00 GMT

TwitterでCommon Lispの変数の種類と特徴について考える機会があったので箇条書き的にまとめてみることにしました。
が、しかし、あまり良くまとめられませんでした…。(エクステントについても触れていません)
変なところがあったら直しますので教えて下さい。
詳しくは、数理システムさんにまとめのドキュメントがあるので参照されると良いかと思います。
Common Lisp の スコープ と エクステント(PDF)

基本的な事項

CLのトップレベルにレキシカルな束縛はない(ヌルレキシカル環境/空レキシカル環境)

;; トップレベルで
(setq foo 33)

foo ;⇒ 33

;; シンボルのバリューセルに値が格納されている (symbol-value 'foo) ;⇒ 33

LETではレキシカルな束縛を作れる(LAMBDAでも可)

(LET ((FOO-LEX 33))
  FOO-LEX)
;⇒ 33

(LET ((FOO-LEX 33)) (SYMBOL-VALUE 'FOO-LEX)) ;>>> The variable FOO-LEX is unbound.

LETはスペシャル変数の束縛も作れる

(LET ((FOO-SPECIAL 33))
  (DECLARE (SPECIAL FOO-SPECIAL))
  FOO-SPECIAL)
;⇒ 33

スペシャル変数はシンボルのバリューセルの値が参照される

(LET ((FOO-SPECIAL 33)) ; FOO-SPECIALはレキシカル変数
  (SYMBOL-VALUE 'FOO-SPECIAL))
;>>> The variable FOO-SPECIAL is unbound.

(LET ((FOO-SPECIAL 33)) (DECLARE (SPECIAL FOO-SPECIAL)) ;スペシャル宣言 (SYMBOL-VALUE 'FOO-SPECIAL)) ;⇒ 33

(PROGV '(FOO-SPECIAL) '(33) (SYMBOL-VALUE 'FOO-SPECIAL)) ;⇒ 33

ローカルなスペシャル宣言は内側の変数の束縛には影響しない

(LET ((FOO-SPECIAL 33))
  (DECLARE (SPECIAL FOO-SPECIAL)) ;ローカルな宣言
  (LET ((FOO-SPECIAL 42))
    (LIST FOO-SPECIAL (SYMBOL-VALUE 'FOO-SPECIAL))))
;⇒ (42 33)

トップレベルでのスペシャル宣言は浸透的にLETなどの内側にも影響し宣言は取り消せない

(DEFVAR TOP-FOO-SPECIAL 100)

(LET ((TOP-FOO-SPECIAL 33)) (LET ((TOP-FOO-SPECIAL 42)) (LIST TOP-FOO-SPECIAL (SYMBOL-VALUE 'TOP-FOO-SPECIAL)))) ;⇒ (42 42)

定数は代入も束縛もできない

(DEFCONSTANT FOO-CONSTANT 42)

(SETQ FOO-CONSTANT 33) ;>> FOO-CONSTANT is a constant and thus can't be set.

(LET ((FOO-CONSTANT 33)) FOO-CONSTANT) ;>> The name of the lambda variable FOO-CONSTANT is already in use to name a constant.

生活の知恵

トップレベルでスペシャル宣言すると同名のシンボルでレキシカル束縛が作れなくなることについて

+ *foo*というように*耳当て*を付けてスペシャル変数であることを字面で分かりやすくする
+ トップレベルではsetや、(setf symbol-value)、setqを使いスペシャル宣言を回避(ちなみにsetqはcmuclではスペシャル宣言されるのがデフォルト動作)

トップレベルからレキシカル変数風の動作が欲しい

DEFINE-SYMBOL-MACROを使う。KMRCLのようなライブラリで定義されていることがある
(defmacro deflex (var val &optional (doc nil docp))
  "Defines a top level (global) lexical VAR with initial value VAL,
      which is assigned unconditionally as with DEFPARAMETER. If a DOC
      string is provided, it is attached to both the name |VAR| and the
      name *STORAGE-FOR-DEFLEX-VAR-|VAR|* as a documentation string of
      kind 'VARIABLE. The new VAR will have lexical scope and thus may
      be shadowed by LET bindings without affecting its global value."
  (let* ((s0 (load-time-value (symbol-name '#:*storage-for-deflex-var-)))
         (s1 (symbol-name var))
         (p1 (symbol-package var))
         (s2 (load-time-value (symbol-name '#:*)))
         (backing-var (intern (concatenate 'string s0 s1 s2) p1)))
    `(progn
      (defparameter ,backing-var ,val ,@(when docp `(,doc)))
      ,@(when docp
              `((setf (documentation ',var 'variable) ,doc)))
      (define-symbol-macro ,var ,backing-var))))
動作
(KL:DEFLEX FOO 42)

(DEFUN F () (INCF FOO))

(LET ((FOO 0)) (DOTIMES (I 10) (F)) FOO) ;⇒ 0 FOO ;⇒ 52

;; しょうがない (LET ((FOO 33)) (DECLARE (SPECIAL FOO)) FOO) ;>> エラー

;; しょうがない (PROGV '(FOO) '(33) FOO) ;⇒ 42

処理系依存の動作

マルチプログラミングについてはCLの仕様では規定されていないのでスレッドの親子関係でのスコープについても処理系依存

処理系依存ではあるものの大体の合意はある様子

スペシャル宣言は、処理系依存の機能で取り消せることもある(例. SBCL)

(PROGN
  (DEFVAR TOP-FOO-SPECIAL 100)

#0=(LET ((TOP-FOO-SPECIAL 33)) (LET ((TOP-FOO-SPECIAL 42)) (PRINT (LIST TOP-FOO-SPECIAL (SYMBOL-VALUE 'TOP-FOO-SPECIAL)))))

;; スペシャル宣言取消し (SETF (SB-INT:INFO :VARIABLE :KIND 'TOP-FOO-SPECIAL) :UNKNOWN)

;; 同じことをしてみる #0# ) ;-> (42 42) ;-> (42 100)

定数宣言は処理系依存の機能で取り消せることもある(例. SBCL)

(DEFCONSTANT FOO-CONSTANT 42)

(SETF (SB-INT:INFO :VARIABLE :KIND 'FOO-CONSTANT) :UNKNOWN)

(SETQ FOO-CONSTANT 33) ;⇒ 33

(LET ((FOO-CONSTANT 42)) FOO-CONSTANT) ;⇒ 42


comments powered by Disqus