Чому показник визначення дефвару працює по-різному без ініціативи?


10

Припустимо, у мене є файл з назвою, elisp-defvar-test.elщо містить:

;;; elisp-defvar-test.el ---  -*- lexical-binding: t -*- 

(defvar my-dynamic-var)

(defun f1 (x)
  "Should return X."
  (let ((my-dynamic-var x))
    (f2)))

(defun f2 ()
  "Returns the current value of `my-dynamic-var'."
  my-dynamic-var)

(provide 'elisp-dynamic-test)

;;; elisp-defvar-test.el ends here

Я завантажую цей файл, а потім заходжу в буфер нуля і запускаю:

(setq lexical-binding t)
(f1 5)
(let ((my-dynamic-var 5))
  (f2))

(f1 5)повертає 5, як очікувалося, що вказує на те, що тіло f1розглядається my-dynamic-varяк динамічно змінена змінна, як очікувалося. Однак остання форма дає помилку змінної пустоти для my-dynamic-var, вказуючи на те, що вона використовує лексичне визначення для цієї змінної. Це, мабуть, суперечить документації для defvar, яка говорить:

defvarФорма також оголошує змінну як «спеціальний», так що вона завжди динамічно пов'язана , навіть якщо lexical-bindingце т.

Якщо я зміню defvarформу в тестовому файлі, щоб надати початкове значення, то змінна завжди трактується як динамічна, як йдеться в документації. Чи може хто-небудь пояснити, чому масштабування змінної визначається тим, чи defvarбуло поставлено початкове значення при оголошенні цієї змінної?

Ось зворотний аналіз помилок, якщо це має значення:

Debugger entered--Lisp error: (void-variable my-dynamic-var)
  f2()
  (let ((my-dynamic-var 5)) (f2))
  (progn (let ((my-dynamic-var 5)) (f2)))
  eval((progn (let ((my-dynamic-var 5)) (f2))) t)
  elisp--eval-last-sexp(t)
  eval-last-sexp(t)
  eval-print-last-sexp(nil)
  funcall-interactively(eval-print-last-sexp nil)
  call-interactively(eval-print-last-sexp nil nil)
  command-execute(eval-print-last-sexp)

4
Я думаю, що обговорення в програмі № 18059 є актуальною.
Василь

Чудове запитання, і так, будь ласка, дивіться обговорення помилки # 18059.
Дрю

Я бачу, так виглядає, що документація буде оновлена ​​для вирішення цього питання в Emacs 26.
Ryan C. Thompson,

Відповіді:


8

Чому двоє трактуються по-різному, це здебільшого "тому, що це нам було потрібно". Більш конкретно, форма одного аргументу defvarз'явилася дуже давно, але пізніше, ніж інша, і в основному була "рушником", щоб заглушити попередження компілятора: на час виконання це не мало ніякого ефекту, тому "аварія" означала що замовчувальна поведінка (defvar FOO)застосовується лише до поточного файлу (оскільки компілятор не міг знати, що таке дефавара виконано в якомусь іншому файлі).

Коли він lexical-bindingбув представлений в Emacs-24, ми вирішили повторно використовувати цю (defvar FOO)форму, але це означає, що тепер вона має ефект.

Частково для збереження попереднього "лише впливає на поточний файл" поведінку, але ще важливіше, щоб дозволити бібліотеці використовувати totoяк динамічно розширений var, не перешкоджаючи іншим бібліотекам використовувати totoв якості лексично обширного var (зазвичай умова іменування пакунків-префіксів уникає цих конфлікти, але він не використовується скрізь сумно), нову поведінку (defvar FOO)було визначено, щоб застосувати лише до поточного файлу, і навіть було вдосконалено, тому воно застосовується лише до поточного обсягу (наприклад, якщо він з'являється у межах функції, це впливає лише на використання що вар у межах цієї функції).

Принципово, (defvar FOO VAL)і (defvar FOO)це лише дві "абсолютно різні" речі. Вони просто використовують одне і те ж ключове слово з історичних причин.


1
+1 для відповіді. Але підхід Спільного Ліспа ясніший і кращий, ІМХО.
Дрю

@Drew: Я в основному погоджуюся, але повторне використання (defvar FOO)нового режиму набагато сумісніше зі старим кодом. Крім того, проблема IIRC щодо рішення CommonLisp полягає в тому, що для чистого інтерпретатора, як Elisp, це досить дорого (наприклад, кожен раз, коли ви оцінюєте, letвам потрібно заглянути всередину його тіла, на випадок, якщо declareце впливає на деякі з них).
Стефан

Домовились про обидва пункти.
Дрю

4

Виходячи з експерименту, я вважаю, що проблема полягає в тому, що (defvar VAR)без значення init впливає лише на бібліотеку (бібліотеки), в якій вона з'являється.

Коли я додав (defvar my-dynamic-var)у *scratch*буфер, помилка більше не сталася.

Спочатку я вважав, що це пов’язано з оцінкою цієї форми, але потім я спершу помітив, що достатньо просто відвідати файл із наявною формою; і крім того, що просто додавання (або видалення) цієї форми в буфері, не оцінюючи її, було достатньо, щоб змінити те, що сталося при оцінці (let ((my-dynamic-var 5)) (f2))всередині цього самого буфера eval-last-sexp.

(Я не маю реального розуміння того, що відбувається тут. Я вважаю поведінку дивним, але я не знайомий з деталями того, як реалізується ця функціональність.)

Додам, що ця форма defvar(без значення init) перешкоджає компілятору байт скаржитися на використання зовнішньо визначеної динамічної змінної у файлі elisp, але сама по собі вона не призводить до того, що ця змінна не буде boundp; тому це не суворо визначає змінну. (Зауважте, що якщо ця змінна була, boundp то ця проблема взагалі не виникала б.)

На практиці я припускаю , що це буде працювати КІ при умови , що ви дійсно включаєте (defvar my-dynamic-var)в будь-який лексичному зв'язуванні бібліотеки , яка використовує ваш my-dynamic-varзмінну (які , імовірно , будуть мати реальне визначення в іншому місці).


Редагувати:

Дякуємо вказівнику від @npostavs у коментарях:

І те, eval-last-sexpі eval-defunвикористання eval-sexp-add-defvarsдля того, щоб:

Додайте EXP до всіх defvars, які передують йому в буфері.

В Зокрема , вона знаходить все defvar, defconstі defcustomекземпляри. (Навіть коли коментую, я помічаю.)

Оскільки це пошук буфера під час виклику, він пояснює, як ці форми можуть мати ефект у буфері навіть без оцінки, і підтверджує, що форма повинна відображатися в тому ж файлі elisp (а також раніше, ніж код, який оцінюється) .


2
IIUC, помилка # 18059 підтверджує ваші вправи.
Василь

2
Здається, що eval-sexp-add-defvarsперевіряє наявність дефеварів у тексті буфера.
npostavs

1
+1. Очевидно, що ця функція не є чіткою або не є чітко представлена ​​користувачам. Виправлення помилок у документі №18059 допомагає, але це все-таки таємниче, якщо не тендітне для користувачів.
Дрю

0

Я взагалі не можу це відтворити, оцінюючи останній фрагмент тут добре, і повертає 5, як очікувалося. Ви впевнені, що не оцінюєте my-dynamic-varсамостійно? Це призведе до помилки, оскільки змінна недійсна, вона не була встановлена ​​на значення, і вона буде мати лише одну, якщо ви динамічно прив’язуєте її до одиниці.


1
Ви встановили lexical-bindingне-нульові показники, перш ніж оцінювати форми? Я розумію поведінку, яку ви описуєте за допомогою lexical-bindingнуля, але коли я встановлюю її на ненульову, я отримую помилку змінної пустоти.
Райан К. Томпсон,

Так, я зберегла це в окремому файлі, повернула назад, перевірила, що lexical-bindingвстановлено та оцінювала форми послідовно.
wasamasa

@wasamasa Відтворює для мене, можливо, ви випадково вказали my-dynamic-varдинамічне значення верхнього рівня у вашому поточному сеансі? Я думаю, що це могло б ознаменувати його постійно особливим.
npostavs
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.