Як змусити повторну оцінку дефавару?


21

Припустимо, у мене буфер Emacs lisp, який містить:

(defvar foo 1)

Якщо я дзвоню eval-last-sexpабо eval-buffer, fooприв'язується до 1. Якщо я потім редагую цей буфер:

(defvar foo 2)

eval-last-sexpі eval-bufferне виконувати повторно цей рядок, так fooце ще 1.

Це особливо складно, коли таких заяв декілька, і я повинен відстежувати, які рядки не піддаються повторній оцінці.

Я переглянув лише перезапуск Emacs, а потім (require 'foo), але тоді я повинен бути обережним, щоб не завантажувати старіші файли .elc.

Як я можу бути абсолютно впевнено впевненим, що змінні та функції, визначені в поточному файлі, перебувають у тому ж стані, що і знову завантаження коду в новому екземплярі Emacs?


Ви не можете бути " абсолютно впевнено впевненим, що Emacs перебуває в стані, такому ж, як знову завантажувати код у новому екземплярі Emacs ", не роблячи тільки цього. Якщо ви хочете бути впевненими лише у wrt цій та інших глобальних змінних , тоді ви можете видалити їх значення за допомогою, makunboundа потім повторно оцінити код у буфері.
Дрю

Звичайно, такі побічні ефекти, як (дурний код), (incf emacs-major-version)я можу жити з тим, що трапляється повторно. Мене цікавить злом коду з великою кількістю defvarформ.
Вільфред Х'юз

Відповіді:


29

Як пояснено в інших відповідях, оцінювання defvarформи за допомогою eval-last-sexpне скидає значення за замовчуванням.

Замість цього, ви можете використовувати eval-defun(зобов'язані C-M-xв emacs-lisp-modeза замовчуванням), який реалізує поведінку , яке ви хочете в якості особливого винятку:

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


Якщо вам потрібно оцінити повний вміст буфера, ви можете написати функцію, яка переходить по черзі форми верхнього рівня і закликає eval-defunкожен. Щось подібне повинно працювати:

(defun my/eval-buffer ()
  "Execute the current buffer as Lisp code.
Top-level forms are evaluated with `eval-defun' so that `defvar'
and `defcustom' forms reset their default values."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (while (not (eobp))
      (forward-sexp)
      (eval-defun nil))))

5
Це відповідь. Не потрібно фальшивих дефаварів або додаткових setqs. Просто використовуйте eval-defunзамість eval-last-sexp . Ви навіть можете записати функцію, яка викликає eval-defunбудь-яку форму в буфер, і використовувати її замість eval-buffer.
Малабарба

1
@Malabarba Це повідомлення далеко не відповідає на запитання. Використовуйте eval-defunзамість eval-last-sexp, звичайно, але складність - для eval-buffer.
Жил "ТАК - перестань бути злим"

@Gilles так, ти маєш рацію. Я додав орієнтовну реалізацію ідеї @ Malabara щодо використання eval-defunбудь-якої форми верхнього рівня в буфері.
ffevotte

1
Цей підхід, здається, не працює, якщо defvarце не всередині a defun. Приклад: (progn (defvar foo "bar")).
Каушал Моді

2
@kaushalmodi Останні приклади, які ви цитуєте (змінні, що зберігають регулярні вирази), дуже схожі на кандидатів defconst(які завжди переоцінюються). Нещодавно на цій темі з'явився дуже просвітницький пост у нескінченних дужках
ffevotte

5

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

Ви можете тимчасово переосмислити, як працює defvar, якщо хочете, і за цей час перезавантажте пакунки, які хочете скинути.

Я написав макрос, де під час оцінювання тіла значення девальваторів завжди будуть переоцінені.

(defmacro my-fake-defvar (name value &rest _)
  "defvar impersonator that forces reeval."
  `(progn (setq ,name ,value)
          ',name))

(defmacro with-forced-defvar-eval (&rest body)
  "While evaluating, any defvars encountered are reevaluated"
  (declare (indent defun))
  (let ((dv-sym (make-symbol "old-defvar")))
    `(let ((,dv-sym (symbol-function 'defvar)))
       (unwind-protect
           (progn
             (fset 'defvar (symbol-function 'my-fake-defvar))
             ,@body)
         (fset 'defvar ,dv-sym)))))

Приклад використання:

file_a.el

(defvar my-var 10)

file_b.el

(with-forced-defvar-eval
  (load-file "file_a.el")
  (assert (= my-var 10))
  (setq my-var 11)
  (assert (= my-var 11)
  (load-file "file_a.el")
  (assert (= my-var 10))

Примітка. Це слід використовувати лише з метою переоцінки дефвараторів, оскільки воно просто ігнорує docstrings при переоцінці. Ви можете змінити макрос, щоб він підтримував переоцінку, яка застосовує доктрини, але це я залишаю вам.

У вашому випадку ви могли б зробити

(with-forced-defvar-eval (require 'some-package))

Але знайте, що роблять ті, хто пише elisp, очікуючи, що defvar працюватиме, як зазначено, можливо, вони використовують defvar для визначення та setq в якійсь функції init, щоб вказати значення, тож ви можете в кінцевому підсумку змінити nil'ing, яких ви не маєте наміру, але це, мабуть, рідко.

Альтернативна реалізація

Використовуючи це, ви можете просто переосмислити defvar у глобальному масштабі та контролювати, чи буде він встановлювати значення символу аргументу INIT-VALUE, навіть якщо символ визначається зміною значення нового defvar-always-reeval-valuesсимволу.

;; save the original defvar definition
(fset 'original-defvar (symbol-function 'defvar))

(defvar defvar-always-reeval-values nil
  "When non-nil, defvar will reevaluate the init-val arg even if the symbol is defined.")

(defmacro my-new-defvar (name &optional init-value docstring)
  "Like defvar, but when `defvar-always-reeval-values' is non-nil, it will set the symbol's value to INIT-VALUE even if the symbol is defined."
  `(progn
     (when defvar-always-reeval-values (makunbound ',name))
     (original-defvar ,name ,init-value ,docstring)))

;; globally redefine defvar to the new form
(fset 'defvar (symbol-function 'my-new-defvar))

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

Якщо вам потрібно переосмислити defvarмакрос, вам, мабуть, буде краще префіксувати початкову defvarформу а makunbound, а не замінювати її на setq.
ffevotte

Так, це жахлива ідея, і її слід використовувати лише для таких речей, як повторне виправлення дефеварів завантаженого пакету у вашому буферному скріпі, ви ніколи не повинні надсилати щось подібне.
Джордон Біондо

@Francesco також ви маєте рацію щодо версії makunbound, я це реалізував, але відхилився від ідеї, я поставив цей код у своїй відповіді як альтернативу.
Джордон Біондо

3

defvarПроводиться оцінка та робити саме те , що ви вказали. Однак defvarвстановлює лише початкове значення:

Необов'язковий аргумент INITVALUE оцінюється та використовується для встановлення SYMBOL, лише якщо значення SYMBOL недійсне.

Отже, щоб досягти того, що ви хочете, вам потрібно буде відв'язати змінну перед повторною оцінкою, наприклад

(makunbound 'foo)

або використовувати setqдля встановлення значення, наприклад

(defvar foo nil "My foo variable.")
(setq foo 1)

Якщо тут вам не потрібно вказувати docstring, ви можете пропустити defvarцілком.

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


Я eval-bufferвідмовився грати з обгорткою, яка спочатку все б розв’язала, але відповідь @ Франческо - eval-defunце те, що ви хочете.
глюкас

1

Наступний макрос був створений шляхом відстеження eval-defunйого підтримуючих функцій та модифікації його таким чином, що більше не потрібно оцінювати область певного буфера. Мені потрібна допомога в пов'язаному потоці Перетворення виразу в рядок , і на допомогу прийшов @Tobias - навчив мене перетворювати недосконалу функцію в макрос. Я не думаю, що нам потрібно eval-sexp-add-defvarsпередувати elisp--eval-defun-1, але якщо хтось вважає, що це важливо, будь ласка, дайте мені знати.

;;; EXAMPLE:
;;;   (defvar-reevaluate
;;;     (defvar undo-auto--this-command-amalgamating "hello-world"
;;;     "My new doc-string."))

(defmacro defvar-reevaluate (input)
"Force reevaluation of defvar."
  (let* ((string (prin1-to-string input))
        (form (read string))
        (form (elisp--eval-defun-1 (macroexpand form))))
    form))

0

Проблема не в тому, що лінія не піддається повторній оцінці. Проблема полягає в тому, що defvarвизначається змінна та її значення за замовчуванням . Якщо змінна вже існує, то зміна її значення за замовчуванням не змінює поточне значення. На жаль, я думаю, вам потрібно буде запустити setqдля кожної змінної, значення якої потрібно оновити.

Це може бути зайвим, але ви можете оновити такий файл, якби ви могли легко оновити fooйого нове значення за замовчуванням.

(defvar foo 2)
(setq foo 2)

але для цього потрібно зберегти значення за замовчуванням у двох місцях коду. Ви також можете це зробити:

(makunbound 'foo)
(defvar foo 2)

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


Це важко при спробі перевірити зміни на складний режим. Я б краще не змінив код. eval-defunлікує defvarспеціально, тож напевно є щось подібне для цілих буферів?
Вільфред Х'юз

@WilfredHughes Я не впевнений, що ти маєш на увазі під "щось для цілих буферів". Ви хочете отримати єдину функцію, яка буде makunboundзмінювати будь-які змінні у поточному буфері, а потім повторно оцінювати її? Ви можете написати свій власний, але я не думаю, що для цього існує функція "поза коробкою". EDIT: Неважливо, я розумію, що ви говорите. Оно eval-defunпрацює на цілому буфері. Схоже, @JordonBiondo має для цього ваше рішення.
nispio

Ні. Проблема полягає у відсутності повторної оцінки: defvarнічого не робить, якщо змінна вже має значення (як каже її документ:) The optional argument INITVALUE is evaluated, and used to set SYMBOL, only if SYMBOL's value is void.. Проблема не в тому, що defvarзмінюється значення за замовчуванням, а не поточне значення. (defvar a 4) (default-value 'a) (setq a 2) (default-value 'a); потім C-x C-eпісля defvarсексопа; то (default-value 'a). C-x C-e, eval-regionі подібне на defvarsexp не змінюють значення за замовчуванням.
Дрю
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.