% Macro DEFPARAMETER, DEFVAR
Macro DEFPARAMETER
defparameter
name initial-value [documentation] => name
defvar
name [initial-value [documentation]] => name
name - シンボル、評価されません。
initial-value - フォーム。
defparameter
は常に評価されます。
しかしdefvar
はnameがbound
ではないときのみ評価されます。
documentation - 文字列、評価されません。
defparameter
とdefvar
は、nameを動的変数として確立します。
defparameter
は、nameという動的変数にinitial-valueを
無条件に割り当てます。
対称的にdefvar
は
nameという変数がbound
ではない場合において、
与えられたinitial-valueの値を割り当てます。
initial-valueが与えられなかったときは、
defvar
はnameの動的変数に手を加えず
値のセルをそのままにします。
もしnameが前にbound
のときは古い値のままにしますし、
以前の値がunbound
であったときはunbound
のままにします。
documentationが与えられたときは、
nameに対してドキュメント文字を種別variable
として割り当てます。
defparameter
とdefvar
は通常トップレベルフォームに現れますが、
非トップレベルフォームに現れても意味があります。
しかし、下記で説明するコンパイル時の副作用は、
defconstant
がトップレベルフォームとして現れた場所でのみ
効果があります。
(defparameter *p* 1) => *P*
*p* => 1
(constantp '*p*) => false
(setq *p* 2) => 2
(defparameter *p* 3) => *P*
*p* => 3
(defvar *v* 1) => *V*
*v* => 1
(constantp '*v*) => false
(setq *v* 2) => 2
(defvar *v* 3) => *V*
*v* => 2
(defun foo ()
(let ((*p* 'p) (*v* 'v))
(bar))) => FOO
(defun bar () (list *p* *v*)) => BAR
(foo) => (P V)
defparameter
とdefvar
の主な操作上の違いは、
defparameter
はnameに無条件にアサインするのに対して、
defvar
は条件付きでそれを行うことにあります。
実際にこれらは
defparameter
はロードやリロード時に
定義で新しい値を変数に設定するときに便利ですし、
defvar
の場合はファイルのロードやリロード時に
古い値をそのままにしておきたいときに便利です。
例として、次のような内容のファイルを示します。
(defvar *the-interesting-numbers* '())
(defmacro define-interesting-number (name n)
`(progn (defvar ,name ,n)
(pushnew ,name *the-interesting-numbers*)
',name))
(define-interesting-number *my-height* 168) ;cm
(define-interesting-number *my-weight* 13) ;stones
この*the-interesting-numbers*
の初期値()
はただの種であり、
一度そこから何かを育てたあとは
他の何かリセットすることはないでしょう。
そのため、defvar
を使うことで、
ファイルを二回読み込んだときに
*interesting-numbers*
の情報が
リセットされないようにしています。
確かにdefine-interesting-number
の
二回の呼び出しは処理されますが、
しかし他のファイルで追加の呼び出しがあったときは
その情報が損失することはありません。
一方、次のコードを見てみます。
(defparameter *default-beep-count* 3)
(defun beep (&optional (n *default-beep-count*))
(dotimes (i n) (si:%beep 1000. 100000.) (sleep 0.1)))
ここでは、コードを編集して*default-beep-count*
の初期値を変更し、
ファイルをリロードして新しい値を取得する
ということを簡単に想像できます。
値の更新を簡単にするために
defparameter
を使用しました。
一方で、このような状況でdefvar
を使うことは潜在的な価値があります。
例えば、誰かが事前に*default-beep-count*
の違う値を定義していたとします。
あるいは、ファイルを読み込んでからその値を手動で変更したとします。
どちらの場合でも、defparameter
のかわりにdefvar
を使用していれば、
ファイルのロード・リロードを行っても
それらのユーザーの好みは上書きされることはありません。
defvar
とdefparameter
のどちらを選んでも、
プログラムには目に見える結果をもたらしますが、
それにもかかわらず主観的な理由で選択されることがあります。
もしdefvar
かdefparameter
が
トップレベルフォームとして現れたときは、
コンパイラーはnameをspecial
としてproclaim
されるよう
認識しなければなりません。
しかし、コンパイル時にinitial-valueフォームが評価されたり、
nameという動的変数に代入してはいけません。
適合するプログラムの正しい動作を妨げない限り、 コンパイル時または実行時に、 追加の(実装で定義された)副作用があってもかまいません。
defvar
は、nameがbound
であるかどうかに影響されます。
なし。
declaim
,
defconstant
,
documentation
,
3.2. コンパイル
慣習として動的変数の名前には
始まりと終わりにアスタリスクを付けたものが使われます。
例えば*foo*
は良い動的変数の名前ですが、
レキシカル変数には使われません。
foo
であれば良いレキシカル変数の名前になりますが、
動的変数には使われません。
この命名規則は、Common Lisp内に定義されたすべての名前で見られますが、
適合するプログラムも適合する実装もこの規則を遵守する義務はありません。
追加で副作用が許可される理由は、
実装がその定義に付随する通常の「帳簿管理」を行えるようにすることです。
例えば、defvar
とdefparameter
フォームのマクロ展開には、
定義が記載されているソースファイルの名前を記録するためにアレンジした
コードが含まれるかもしれません。
defparameter
とdefvar
は下記のように定義されます。
(defmacro defparameter (name initial-value
&optional (documentation nil documentation-p))
`(progn (declaim (special ,name))
(setf (symbol-value ',name) ,initial-value)
,(when documentation-p
`(setf (documentation ',name 'variable) ',documentation))
',name))
(defmacro defvar (name &optional
(initial-value nil initial-value-p)
(documentation nil documentation-p))
`(progn (declaim (special ,name))
,(when initial-value-p
`(unless (boundp ',name)
(setf (symbol-value ',name) ,initial-value)))
,(when documentation-p
`(setf (documentation ',name 'variable) ',documentation))
',name))