npt-japanese

% Macro DEFPARAMETER, DEFVAR

UP


Macro DEFPARAMETER, DEFVAR

Macro DEFPARAMETER

構文

defparameter name initial-value [documentation] => name
defvar name [initial-value [documentation]] => name

引数と戻り値

name - シンボル、評価されません。
initial-value - フォーム。 defparameterは常に評価されます。 しかしdefvarnameboundではないときのみ評価されます。
documentation - 文字列、評価されません。

定義

defparameterdefvarは、nameを動的変数として確立します。

defparameterは、nameという動的変数にinitial-valueを 無条件に割り当てます。 対称的にdefvarnameという変数がboundではない場合において、 与えられたinitial-valueの値を割り当てます。

initial-valueが与えられなかったときは、 defvarnameの動的変数に手を加えず 値のセルをそのままにします。 もしnameが前にboundのときは古い値のままにしますし、 以前の値がunboundであったときはunboundのままにします。

documentationが与えられたときは、 nameに対してドキュメント文字を種別variableとして割り当てます。

defparameterdefvarは通常トップレベルフォームに現れますが、 非トップレベルフォームに現れても意味があります。 しかし、下記で説明するコンパイル時の副作用は、 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)

defparameterdefvarの主な操作上の違いは、 defparameternameに無条件にアサインするのに対して、 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を使用していれば、 ファイルのロード・リロードを行っても それらのユーザーの好みは上書きされることはありません。

defvardefparameterのどちらを選んでも、 プログラムには目に見える結果をもたらしますが、 それにもかかわらず主観的な理由で選択されることがあります。

副作用

もしdefvardefparameterが トップレベルフォームとして現れたときは、 コンパイラーはnamespecialとしてproclaimされるよう 認識しなければなりません。 しかし、コンパイル時にinitial-valueフォームが評価されたり、 nameという動的変数に代入してはいけません。

適合するプログラムの正しい動作を妨げない限り、 コンパイル時または実行時に、 追加の(実装で定義された)副作用があってもかまいません。

影響

defvarは、nameboundであるかどうかに影響されます。

例外

なし。

参考

declaim, defconstant, documentation, 3.2. コンパイル

備考

慣習として動的変数の名前には 始まりと終わりにアスタリスクを付けたものが使われます。 例えば*foo*は良い動的変数の名前ですが、 レキシカル変数には使われません。 fooであれば良いレキシカル変数の名前になりますが、 動的変数には使われません。 この命名規則は、Common Lisp内に定義されたすべての名前で見られますが、 適合するプログラムも適合する実装もこの規則を遵守する義務はありません。

追加で副作用が許可される理由は、 実装がその定義に付随する通常の「帳簿管理」を行えるようにすることです。 例えば、defvardefparameterフォームのマクロ展開には、 定義が記載されているソースファイルの名前を記録するためにアレンジした コードが含まれるかもしれません。

defparameterdefvarは下記のように定義されます。

(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))

TOP, Github