Как мне принудительно переоценить дефвар?

22

Предположим, у меня есть буфер 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 », не делая этого. Если вы хотите быть уверенным только в отношении этой и других глобальных переменных , то вы можете удалить их значения, используя, makunboundа затем пересмотреть код в буфере.
Дрю
Конечно, побочные эффекты, такие как (глупый код), (incf emacs-major-version)с которыми я могу жить неоднократно. Я заинтересован во взломе кода с множеством defvarформ.
Уилфред Хьюз

Ответы:

30

Как объяснено в других ответах, оценка defvarформы с помощью eval-last-sexpне сбрасывает значение по умолчанию.

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

Если текущий defun на самом деле является вызовом 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))))
ffevotte
источник
5
Это ответ. Нет необходимости в поддельных defvars или дополнительных setqs. Просто используйте eval-defunвместо eval-last-sexp . Вы даже можете написать функцию, которая вызывает eval-defunкаждую форму в буфере, и использовать ее вместо eval-buffer.
Малабарба
1
@Malabarba Этот пост далек от ответа на вопрос. Используйте eval-defunвместо eval-last-sexp, конечно, но сложность для eval-buffer.
Жиль "ТАК - перестань быть злым"
@ Жиль, да, ты прав. Я добавил предварительную реализацию идеи @ Malabara о вызове eval-defunкаждой формы верхнего уровня в буфере.
ffevotte
1
Этот подход, похоже, не работает, если defvarне внутри defun. Пример: (progn (defvar foo "bar")).
Каушал Моди
2
@kaushalmodi Последние примеры, которые вы приводите (переменные, хранящие регулярные выражения), очень похожи на кандидатов defconst(которые всегда переоцениваются). Недавно в бесконечных скобках
ffevotte
5

Как и в других ответах, именно так работает defvar, но вы можете обойти это, в конце концов, это elisp.

Вы можете временно переопределить работу defvar, если хотите, и в течение этого времени перезагрузить пакеты, которые вы хотите сбросить.

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

(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))

Примечание. Это следует использовать только с целью переоценки defvars, поскольку при переоценке он просто игнорирует строки документации. Вы можете изменить макрос для поддержки переоценки, которая также применяет строки документов, но я оставлю это на ваше усмотрение.

В вашем случае вы могли бы сделать

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

Но знайте, что те, кто пишут elisp, делают так, ожидая, что defvar будет работать как указано, возможно, они используют defvar для определения и setq в некоторой функции init для указания значения, так что вы можете в конечном итоге обнулить переменные, которые вы не намереваетесь, но это, наверное, редко.

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

Используя это, вы можете просто переопределить 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
Да, это ужасная идея, и ее следует использовать только для таких вещей, как повторное вычисление defvars загруженного пакета в вашем чистом буфере, вы никогда не должны отправлять что-то подобное.
Джордон Биондо
@Francesco также вы правы насчет makunbound версии, я реализовал это, но отошел от идеи, я добавил этот код в свой ответ в качестве альтернативы.
Джордон Биондо
3

defvarПроводится оценка и делать именно то , что вы указали. Тем не менее, defvarтолько устанавливает начальное значение:

Необязательный аргумент INITVALUE оценивается и используется для установки SYMBOL, только если значение SYMBOL равно void.

Таким образом, чтобы достичь желаемого, вам нужно либо отменить привязку переменной перед повторной оценкой, например,

(makunbound 'foo)

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

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

Если вам не нужно указывать здесь строку документации, вы можете defvarвообще пропустить .

Если вы действительно хотите использовать defvarи автоматически отсоединять это, вам нужно написать функцию для поиска defvarвызовов в текущем буфере (или области, или последнем сексе и т. Д.); позвонить makunboundдля каждого; а затем сделать фактический Eval.

glucas
источник
Я поиграл с eval-bufferоберткой, которая сначала свяжет все, но ответ @ Francesco о eval-defunтом, что вы действительно хотите.
glucas
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))
lawlist
источник
0

Проблема не в том, что линия не переоценивается. Проблема в том, что defvarопределяет переменную и ее значение по умолчанию . Если переменная уже существует, то изменение ее значения по умолчанию не изменяет текущее значение. К сожалению, я думаю, вам нужно будет запустить setqдля каждой переменной, значение которой вы хотите обновить.

Это может быть излишним, но вы можете обновить свой файл, как этот, если вы хотите иметь возможность легко обновить fooего до нового значения по умолчанию.

(defvar foo 2)
(setq foo 2)

но это требует, чтобы вы поддерживали значение по умолчанию в двух местах в вашем коде. Вы также можете сделать это:

(makunbound 'foo)
(defvar foo 2)

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

nispio
источник
Это сложно при попытке проверить изменения в сложном режиме. Я бы не хотел менять код. eval-defunлечит defvarспециально, поэтому наверняка есть что-то похожее для целых буферов?
Уилфред Хьюз
@WilfredHughes Я не уверен, что вы подразумеваете под «что-то для целых буферов». Вам нужна единственная функция, которая будет makunboundопределять любые переменные, объявленные в текущем буфере, и затем переоценивать ее? Вы можете написать свой собственный, но я не думаю, что для этого есть готовая функция. РЕДАКТИРОВАТЬ: Неважно, я понимаю, что вы говорите. Это eval-defunработает на весь буфер. Похоже, у @JordonBiondo есть для этого решение.
Ниспио
Нет. Проблема заключается в отсутствии переоценки: 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 ничего не изменить значение по умолчанию.
Дрю