setq и defvar в Лиспе - PullRequest
       30

setq и defvar в Лиспе

47 голосов
/ 04 октября 2010

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

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

Ответы [ 4 ]

45 голосов
/ 04 октября 2010

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

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, ... Почему? Потому что тогда эти переменные будут объявлены специальными, и это трудно отменить. Специальное объявление является глобальным, и при дальнейшем использовании переменной будет использоваться динамическое связывание.

BAD:

(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, компилятор должен генерировать код для динамического поиска. Затем какой-то компилятор говорит: «Хорошо, мы предполагаем, что это динамический поиск, давайте объявим, что он special - поскольку это то, что мы предполагаем в любом случае.

18 голосов
/ 04 октября 2010

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
8 голосов
/ 04 октября 2010

defvar и defparameter оба представляют глобальные переменные. Как отмечает Кен, setq присваивает переменной.

Кроме того, defvar не будет забивать что-то ранее defvar -ed. Позже в книге (глава 6) Сейбел говорит: «На практике вы должны использовать DEFVAR для определения переменных, которые будут содержать данные, которые вы хотите сохранить, даже если вы внесли изменения в исходный код, который использует переменную». *

http://www.gigamonkeys.com/book/variables.html

Например, если у вас есть глобальная *db* для базы данных в главе «Простая база данных»:

(defvar *db* nil)

... и вы начинаете играть с ним на REPL - добавлять, удалять вещи и т. Д. - но затем вы вносите изменения в исходный файл, который содержит эту форму defvar, перезагрузка этого файла не уничтожит *db* все изменения, которые вы могли внести ... Я верю, что setq будет, как и defparameter. Более опытный Лиспер, пожалуйста, поправьте меня, если я ошибаюсь.

8 голосов
/ 04 октября 2010

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

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

...