Что происходит, когда специальная переменная объявляется во время компиляции

#lisp #common-lisp #dynamic-binding

#lisp #common-lisp #динамическая привязка

Вопрос:

Я только что столкнулся с необычной ситуацией в моем common lisp-коде, когда я хочу протестировать locally и declare :

 (defvar test-out 2) ;; make a dynamic variable

;; function below just simply re-write from locally doc
(defun test (out)
  (declare (special out))
  (let ((out 1))
    (print out) ;; => 1
    (print (locally (declare (special out)) out)))) ;; => 2

;; when argument has same name as outside dynamic variable
(defun test1 (test-out)
  (declare (special test-out))
  (let ((test-out 1))
    (print test-out) ;; => 1
    (print (locally (declare (special test-out)) test-out)))) ;; => also 1
  

Я знаю, что правильное имя динамической переменной должно быть *test-out* , но я подумал, что программисту удобно указывать динамическую переменную.

Меня немного смущает test1 функция, похоже locally declare , не указывает test-out на динамическую переменную снаружи.

Кто-нибудь может объяснить мне test1 поведение функции? Спасибо

Обновить:

  1. Я даю новую динамическую переменную (defvar test-out-1 3) и вызываю ее как (test1 test-out-1) , все равно получаю результат печати 1 и 1 .
  2. Я меняю test1 имя аргумента с test-out на test-out1 , перекомпилирую test1 , и проблема исчезает, распечатываю результаты 1 и 2 при вызове (test1 test-out) .
  3. Я меняю (defvar test-out 2) на (defvar test-out-1 2) (изменить имя динамической переменной). Затем перекомпилируйте весь файл ( test-out на этот раз динамическая переменная не вызывается, а test1 имя аргумента равно test-out ), проблема исчезнет.
  4. После 3 я вызываю (defvar test-out 2) , и (test1 test-out) . На этот раз он выводит правильные ответы: 1 и 2 .
  5. После 4 я test1 снова перекомпилирую, затем запускаю (test1 test-out) , он распечатывается 1 , и 1 снова появляется проблема.

Если я правильно догадываюсь, при test1 компиляции по какой-то причине имя ее аргументов подключается к динамической переменной test-out . Вот почему я получаю неверный результат, когда я даже вызываю с другим значением, однако проблема решается сама собой, когда я перекомпилирую test1 с другим именем аргумента или очищаю динамическую переменную test-out перед повторной компиляцией теста.

Если это так, я все еще не понимаю, почему функция компиляции будет действовать с помощью динамической переменной в среде.

Ответ №1:

DEFVAR объявляет переменную специальной — это означает, что они будут использовать динамические привязки при привязке, и доступ к такой переменной будет искать динамические привязки. Глобально и на всех уровнях привязки. На данный момент и в будущем.

С этого момента ВСЕ виды использования и привязки этой переменной в новом коде будут автоматически объявлены специальными. Даже локальные привязки LET. На всех уровнях. Невозможно объявить ее неспециалистом. Таким образом, локальное специальное объявление в вашей test1 функции теперь не требуется, оно уже объявлено специальным. Каждое ее использование или привязка, даже без явного объявления, теперь использует динамическую привязку.

Это также причина, по которой любая DEFVAR DEFPARAMETER переменная or должна быть записана как *variablename* , чтобы предотвратить случайное объявление всех переменных с одинаковым именем как специальных.

Избегайте:

 (defvar x 10)         ; here X is explicitly declared special

(defun foo (x)        ; here X is implicitly declared special
  (let ((x ...))      ; here X is implicitly declared special
    ...))   
  

Сделать:

 (defvar *x* 10)       ; here *X* is declared special

(defun foo (x)        ; here X is lexical
  (let ((x ...))      ; here X is lexical
    ...))   
  

Комментарии:

1. Итак, для моего обновления 4 (defvar test-out 2) после test1 компиляции не будет действовать test1 , потому что оно уже скомпилировано?

2. @ccQpein: правильно, DEFVAR обычно не влияет на ранее скомпилированный код. Если ранее скомпилированный код был скомпилирован для использования лексической привязки, это не будет изменено позже, когда DEFVAR будет скомпилирован / загружен / выполнен.

3. @Rainer Joswig, Не было бы также приемлемо сделать: (defvar *x* 10) (defun foo () (let ((*x* ...)) ...))