DEFPARAMETER, DEFVAR — 전역 동적 변수 선언하기

DEFPARAMETER, DEFVAR — 전역 동적 변수 선언하기 (매크로)

전역 dynamic variable을 선언하는 두 매크로예요. 둘 다 변수를 만들지만 초기값을 언제 넣느냐가 달라요. defparameter는 선언할 때마다 항상 초기값을 다시 넣고, defvar는 변수가 아직 바인딩되지 않았을 때만 초기값을 넣어요.

출처: DEFPARAMETER, DEFVAR - Common Lisp HyperSpec

시그니처 (Syntax)

defparameter name initial-value [documentation] => name

defvar name [initial-value [documentation]] => name
  • namesymbol. 평가되지 않아요.
  • initial-valueform. defparameter에서는 항상 evaluate되고, defvar에서는 name이 이미 bound되지 않은 경우에만 evaluate돼요.
  • documentationstring. 평가되지 않아요.

본문 (Description)

defparameterdefvarnamedynamic variableestablish해요.

defparameter는 무조건적으로 initial-valuename이 가리키는 dynamic variableassign해요. 반면 defvarname이 이미 bound되지 않은 경우에만 (제공된다면) initial-value를 그 dynamic variableassign해요.

initial-value가 제공되지 않으면 defvarname이 가리키는 dynamic variablevalue cell을 그대로 두어요. name이 이전에 bound되어 있었으면 옛 value가 유지되고, 이전에 unbound였으면 unbound로 남아요.

documentation이 주어지면 namevariable 종류의 documentation string으로 붙어요.

defparameterdefvar는 보통 top level form으로 나타나지만 non-top-level form으로 나타나도 의미가 있어요. 다만 아래에 설명하는 컴파일 타임 부수효과는 top level form으로 나타날 때만 일어나요.

defparameter와 defvar의 차이

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의 초기값 ()는 단순한 시드(seed)예요. 그걸로 무언가 자란 뒤에 다른 것으로 리셋하길 원하지 않을 거예요. 그래서 defvar를 써서 파일을 두 번째로 로드해도 interesting-numbers 정보가 리셋되지 않게 했어요. 여기서 두 번의 define-interesting-number 호출은 다시 처리되지만, 다른 파일에 추가 호출이 있으면 그건 다시 처리되지 않아서 정보가 잃을 수 있어요(그래서 defvar가 필요하죠).

반면 다음 코드를 보세요:

(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를 썼다면, 그런 사용자 선호가 (다시) 로드로 덮어쓰이지 않았을 거예요.

defparameterdefvar 중 무엇을 쓸지의 선택은 프로그램에 눈에 보이는 결과가 있지만, 그래도 종종 주관적인 이유로 결정돼요.

예제 (Examples)

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

Side Effects

defvardefparameter formtop level form으로 나타나면, compilernamespecial로 선언되었음을 알아야 해요. 다만 컴파일 시점에 initial-value formevaluate하거나 name이 가리키는 dynamic variableassign해서는 안 돼요.

conforming program의 올바른 동작을 방해하지 않는 한, 추가적인(implementation-defined) 컴파일 타임·런타임 부수효과가 있을 수 있어요.

더 알아보기 (See Also)

declaim, defconstant, documentation, Section 3.2 (Compilation)

Notes

dynamic variable은 관례상 이름 앞뒤에 asterisk를 붙여요. 예를 들어 foodynamic variable로는 좋은 이름이지만 lexical variable로는 어울리지 않아요. 반대로 foo는 lexical variable로는 좋은 이름이지만 dynamic variable으로는 어울리지 않아요. 이 명명 관례는 Common Lisp의 모든 defined name에서 지켜지지만, conforming program이나 conforming implementation 모두 이 관례를 따를 의무는 없어요.

추가 부수효과를 허용하는 의도는 implementation이 정의에 수반되는 일반적인 "bookkeeping"을 하게 하려는 거예요. 예를 들어 defvardefparameter formmacro expansion에는 정의가 나타난 원본 파일 이름을 기록하는 코드가 포함될 수 있어요.

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