variables - lists - operator lisp




setq и defvar в Lisp (3)

Я вижу, что Practical Common Lisp использует (defvar *db* nil) для настройки глобальной переменной . Разве это не нормально использовать setq с той же целью?

Каковы преимущества / недостатки использования defvar vs. setq ?


DEFVAR устанавливает новую переменную. SETQ присваивает переменной.

Большинство применений Lisp, которые я использовал, выдает предупреждение, если вы установили SETQ в переменную, которая еще не существует.


defvar вводит динамическую переменную, а setq используется для назначения значения динамической или лексической переменной. Значение динамической переменной просматривается в среде, вызывающей функцию, а значение лексической переменной просматривается в среде, где определена функция. В следующем примере будет понятно:

;; dynamic variable sample
> (defvar *x* 100)
*X*
> (defun fx () *x*)
FX
> (fx)
100
> (let ((*x* 500)) (fx)) ;; gets the value of *x* from the dynamic scope.
500
> (fx) ;; *x* now refers to the global binding.
100

;; example of using a lexical variable
> (let ((y 200))
   (let ((fy (lambda () (format t "~a~%" y))))
     (funcall fy) ;; => 200
     (let ((y 500))
       (funcall fy) ;; => 200, the value of lexically bound y
       (setq y 500) ;; => y in the current environment is modified
       (funcall fy)) ;; => 200, the value of lexically bound y, which was 
                     ;; unaffected by setq
     (setq y 500) => ;; value of the original y is modified.
     (funcall fy))) ;; => 500, the new value of y in fy's defining environment.

Динамические переменные полезны для передачи значения по умолчанию. Например, мы можем привязать динамическую переменную *out* к стандартным выводам, чтобы она стала стандартным выходом всех функций io. Чтобы отменить это поведение, мы просто вводим локальную привязку:

> (defun my-print (s)
        (format *out* "~a~%" s))
MY-PRINT
> (my-print "hello")
hello
> (let ((*out* some-stream))
    (my-print " cruel ")) ;; goes to some-stream
> (my-print " world.")
world

Общее использование лексических переменных заключается в определении замыканий, для эмулирования объектов с состоянием. В первом примере переменная y в среде привязки fy эффективно стала частным состоянием этой функции.

defvar присваивает значение переменной только в том случае, если она еще не назначена. Таким образом, следующее переопределение *x* не изменит первоначальную привязку:

> (defvar *x* 400)
*X*
> *x*
100

Мы можем присвоить новое значение *x* , используя setq :

> (setq *x* 400)
400
> *x*
400
> (fx)
400
> (let ((*x* 500)) (fx)) ;; setq changed the binding of *x*, but 
                         ;; its dynamic property still remains.
500
> (fx)
400

Существует несколько способов введения переменных.

DEFVAR и DEFPARAMETER представляют глобальные динамические переменные. DEFVAR опционально устанавливает его на некоторое значение, если оно уже не определено. DEFPARAMETER всегда задает значение. SETQ не вводит переменную.

(defparameter *number-of-processes* 10)

(defvar *world* (make-world))     ; the world is made only once.

Обратите внимание, что вы, вероятно, никогда не хотите переменные DEFVAR с такими именами, как x , y , stream , limit , ... Почему? Потому что эти переменные тогда будут объявлены специальными, и их трудно отменить. Специальное объявление является глобальным, и все дальнейшие применения переменной будут использовать динамическое связывание.

ПЛОХО:

(defvar x 10)     ; global special variable X, naming convention violated
(defvar y 20)     ; global special variable Y, naming convention violated

(defun foo ()
  (+ x y))        ; refers to special variables X and y

(defun bar (x y)  ; OOPS!! X and Y are special variables
                  ; even though they are parameters of a function!
  (+ (foo) x y))

(bar 5 7)         ; ->   24

ЛУЧШЕ: всегда отмечайте специальные переменные * в своих именах!

(defvar *x* 10)     ; global special variable *X*
(defvar *y* 20)     ; global special variable *Y*

(defun foo ()
  (+ *x* *y*))      ; refers to special variables X and y

(defun bar (x y)    ; Yep! X and Y are lexical variables
  (+ (foo) x y))

(bar 5 7)           ;  ->   42

Локальные переменные вводятся с помощью DEFUN , LAMBDA , LET , MULTIPLE-VALUE-BIND и многих других.

(defun foo (i-am-a-local-variable)
   (print i-am-a-local-variable))

(let ((i-am-also-a-local-variable 'hehe))
  (print i-am-also-a-local-variable))

Теперь по умолчанию локальные переменные в двух формах являются лексическими, если только они не объявлены SPECIAL . Тогда они будут динамическими переменными.

Далее, есть также несколько форм, чтобы установить переменную в новые значения. SET , SETQ , SETF и другие. SETQ и SETF могут устанавливать как лексические, так и специальные (динамические) переменные.

Для переносимого кода требуется, чтобы один задавал переменные, которые уже были объявлены. Точный эффект установки не объявленной переменной не определен стандартом.

Итак, если вы знаете, что делает ваша реализация Common Lisp, вы можете использовать

(setq world (make-new-world))

в Read-Eval-Print-Loop на верхнем уровне. Но не используйте его в своем коде, так как эффект не переносимый. Обычно SETQ задает переменную. Но некоторые реализации могут также объявить переменную SPECIAL, когда она этого не знает (CMU Common Lisp делает это по умолчанию). Это почти всегда не то, что нужно. Используйте его для случайного использования, если вы знаете, что делаете, но не для кода.

Тоже самое:

(defun make-shiny-new-world ()
  (setq world (make-world 'shiny)))

Во-первых, такие переменные должны быть записаны как *world* (с окружающими * символами), чтобы было ясно, что это глобальная специальная переменная. Во-вторых, это должно было быть объявлено с DEFVAR или DEFPARAMETER раньше.

Типичный компилятор Lisp будет жаловаться на то, что указанная выше переменная не объявлена. Поскольку глобальные лексические переменные не существуют в Common Lisp, компилятор должен генерировать код для динамического поиска. Некоторые компиляторы тогда говорят, хорошо, мы предполагаем, что это динамический поиск, давайте объявим его особенным - так как это то, что мы предполагаем в любом случае.





variable-declaration