変数とスコープ


変数とスコープ

Common Lispでの変数は、大きく分けて3種類あります。 (ここでは、定数も変数に含めて話をします)

定数変数

defconstantで定義でき、再代入も再バインドもできません。

「本物の」定数(数学的な定数など)に使います。逆に言うと、それ以外の場合にはあまり使う機会は少ないでしょう。慣例により、定数変数名の前後に+を付けることが多いです。

(defconstant +pi+ 3.14)

+pi+
; => 3.14

;;; 再バインドしようとするとエラーになる
(let ((+pi+ 3.1415))
  (+pi+))
; => ERROR

スペシャル変数(ダイナミック変数)

スペシャル変数(またはダイナミック変数)は、他の言語でいうところのグローバル変数に相当します(ただし、いくつか特長があります)。スペシャル変数は、慣例により名前の前後に*を付けて表され、setfによる代入が可能です。

スペシャル変数を定義するには、defvar を使う方法と defparameter を使う方法があります。 2つの違いは、defvar は初期値なしでもスペシャル変数を定義でき、初期値を指定した場合は、その変数が未定義だった場合のみ初期値を代入します。一方、defparameterは、初期値を必ず指定する必要があり、すでにその変数に値が存在するかどうかにかかわらず初期値を設定します。

(defvar *foo* 1)
*foo*
; => 1

(defvar *bar*)
*bar*
; => エラー( *bar* は unbound )


(defparameter *baz* "baz")
*baz*
; => "baz"

スペシャル変数は、let 形式を用いて実行時に再束縛することができます。let が終了すると、再び以前の値に戻ります。(実行時に決定されることが、「ダイナミック変数」と呼ばれる理由です)。 詳細は、HyperSpecのletの例を参照して下さい。

(defparameter *foo* 100)
(defun print-foo ()
  (format t "~D~%" *foo*))

(print-foo)
;-> 100

(let ((*foo* 200))
  (print-foo))
;-> 200

(print-foo)
;-> 100

レキシカル変数

let 形式を使って、ローカルに使う変数を定義できます。他の言語でのローカル変数に相当します。変数は内側のスコープで再バインドでき、最も内側の let 形式でバインドされた値が使用されます。スコープはレキシカルになります。

(let ((a 10))
  (format t "~D~%" a)
  (let ((a 20))
    (format t "~D~%" a))
  (format t "~D~%" a))
;-> 10
    20
    10

レキシカル変数はちょうど malloc 的にヒープにメモリを確保し、 メモリの解法はGCが行っているのだと考えていいでしょう。 しかし、「ほんとうにレキシカルじゃないといけない」わけではないとわかっている時には、 賢いコンパイラはスタック上にメモリを配置します。 上の例は、実はレキシカル変数でなくてもダイナミック変数でも動きます。 一方下の例はレキシカル変数でなくてはなりません。

(defvar *fn*)

;;レキシカル ver.
(let ((a 0))
  (setf *fn* (lambda () (print (incf a)))))

(funcall *fn*) ; -> 1
(funcall *fn*) ; -> 2
(funcall *fn*) ; -> 3

;;スペシャル ver.
(defvar *a*)
(let ((*a* 0))
  (setf *fn* (lambda () (print (incf *a*)))))

(funcall *fn*) ; Error: The variable *A* is unbound.

C言語的に考えれば、ダイナミックな束縛は、束縛コードの最後に free が行われているのと同様だと考えてみればいいのではないでしょうか。

トップレベルのレキシカル変数

Common Lispにトップレベルのレキシカル変数はありませんが、動作を模倣することは可能です。

手引書