defvar의 재평가를 어떻게 강제합니까?


21

다음을 포함하는 Emacs lisp 버퍼가 있다고 가정하십시오.

(defvar foo 1)

내가 호출하는 경우 eval-last-sexpeval-buffer, foo나는 다음이 버퍼를 편집하는 경우 1로 바인딩 :

(defvar foo 2)

eval-last-sexp그리고 eval-buffer하지 않도록,이 라인을 다시 실행 foo한이 아직도있다.

이러한 진술이 여러 개있을 때 이것은 특히 어려우며 재평가되지 않는 라인을 추적해야합니다.

Emacs를 다시 시작한 다음 (require 'foo).elc 파일을로드하지 않도록주의해야합니다.

현재 파일에 정의 된 변수와 함수가 새로운 Emacs 인스턴스에서 코드를 새로로드하는 것과 동일한 상태에 있는지 어떻게 확실하게 확신 할 수 있습니까?


" Emacs가 새로운 Emacs 인스턴스에서 코드를 새로로드하는 것과 동일한 상태에 있다는 것을 절대적으로 확신 할 수는 없습니다" . 이 변수 와 다른 전역 변수 만 사용 하고 싶다면 버퍼를 사용하여 값을 제거한 다음 코드를 다시 평가하십시오. makunbound
Drew

물론, (어리석은 코드)와 같은 부작용은 (incf emacs-major-version)반복적으로 일어날 수 있습니다. defvar양식 이 많은 코드를 해킹하는 데 관심이 있습니다.
Wilfred Hughes

답변:


29

다른 답변에서 설명한 것처럼 defvar양식을 사용하여 평가 eval-last-sexp해도 기본값이 재설정되지 않습니다.

대신, 당신은 특별한 예외로서 당신이 원하는 행동을 구현하는 (기본적 eval-defun으로 C-M-xin-in emacs-lisp-mode) 을 사용할 수 있습니다 :

현재 defun이 실제로 defvar또는 defcustom에 대한 호출 인 경우이 방법으로 평가하면 변수에 이미 다른 값이있는 경우에도 초기 값 표현식을 사용하여 변수가 재설정됩니다. (일반적으로 defvardefcustom이미 존재하는 경우 값을 변경하지 않습니다.)


버퍼의 전체 내용을 평가해야하는 경우 최상위 양식을 차례로 이동 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
이것이 답입니다. 가짜 defvar 또는 추가 setq가 필요하지 않습니다. eval-defun대신에 사용하십시오 eval-last-sexp . eval-defun버퍼의 모든 양식 을 호출하는 함수를 작성하여 대신 사용할 수 eval-buffer있습니다.
Malabarba

1
@Malabarba이 게시물은 질문에 대답하는 데 훨씬 부족합니다. eval-defun대신에 사용 eval-last-sexp하지만 어려움은입니다 eval-buffer.
Gilles 'SO- 악마 그만해

@Gilles 네, 맞습니다. eval-defun버퍼의 모든 최상위 폼 을 호출 하는 @Malabara의 아이디어를 임시로 구현했습니다 .
ffevotte

1
defvar안에 있지 않으면이 방법이 작동하지 않는 것 같습니다 defun. 예 : (progn (defvar foo "bar")).
Kaushal Modi

2
@kaushalmodi 인용하는 후자의 예제 (정규 표현식을 저장하는 변수)는 후보 defconst(예 : 항상 재평가) 와 매우 유사 합니다. 최근에 끝없는 괄호
있었습니다

5

다른 답변에서 알 수 있듯이 이것은 defvar이 작동하는 방식 일뿐이지만 해결할 수 있습니다.

원하는 경우 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))

참고 : 이것은 재평가시 docstring을 무시하므로 defvar를 재평가 할 목적으로 만 사용해야합니다. docstring도 적용하는 재평가를 지원하기 위해 매크로를 수정할 수는 있지만 그 내용은 그대로 두겠습니다.

귀하의 경우에는 할 수 있습니다

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

그러나 elisp를 작성하는 사람들은 defvar이 지정된대로 작동하기를 기대하므로 defvar을 사용하여 값을 지정하기 위해 init 함수에서 일부 q를 정의하고 setq를 사용할 수 있습니다. 이것은 아마도 드물다.

대체 구현

이를 사용하면 defvar를 전체적으로 재정의하고 새 defvar-always-reeval-values심볼 의 값을 변경하여 심볼이 정의 된 경우에도 심볼 값을 INIT-VALUE arg로 설정할지 여부를 제어 할 수 있습니다 .

;; 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

예, 끔찍한 아이디어이며 스크래치 버퍼에로드 된 패키지의 defvar를 재평가하는 것과 같은 경우에만 사용해야합니다. 이와 같은 것을 배송해서는 안됩니다.
Jordon Biondo

@Francesco 또한 당신은 makunbound 버전에 대해 맞습니다. 나는 그것을 구현했지만 아이디어에서 벗어났습니다. 나는 그 코드를 대안으로 내 대답에 넣었습니다.
Jordon Biondo

3

defvar평가하고 지정한 정확하게 일을하고있다. 그러나 defvar초기 값 만 설정합니다.

선택적 인수 INITVALUE가 평가되고 SYMBOL의 값이 void 인 경우에만 SYMBOL을 설정하는 데 사용됩니다.

따라서 원하는 것을 달성하려면 다시 평가하기 전에 변수를 바인딩 해제해야합니다. 예 :

(makunbound 'foo)

또는 setq값을 설정하는 데 사용합니다. 예 :

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

여기에서 docstring을 지정할 필요가 없다면 defvar완전히 건너 뛸 수 있습니다 .

실제로 defvar이것을 사용 하고 자동으로 바인딩을 해제 defvar하려면 현재 버퍼 (또는 리전 또는 마지막 sexp 등)에서 호출 을 찾는 함수를 작성해야합니다 . makunbound각각을 부르십시오 . 그런 다음 실제 평가를 수행하십시오.


나는 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))

0

문제는 라인이 재평가되지 않는 것이 아닙니다. 문제는 defvar변수와 기본값 을 정의 한다는 것 입니다. 변수가 이미 존재하면 기본값을 변경해도 현재 값이 수정되지 않습니다. 불행히도, setq값을 업데이트하려는 모든 변수에 대해 를 실행해야한다고 생각합니다 .

이것은 과도 할 수 있지만 foo새로운 기본값 으로 쉽게 업데이트 하려면 파일을 이와 같이 업데이트 할 수 있습니다 .

(defvar foo 2)
(setq foo 2)

그러나 코드의 두 위치에서 기본값을 유지해야합니다. 당신은 또한 이것을 할 수 있습니다 :

(makunbound 'foo)
(defvar foo 2)

그러나 foo다른 곳에서 선언 된 기회가 있다면 다루어야 할 부작용이있을 수 있습니다.


복잡한 모드로 변경 사항을 테스트하려고 할 때 어렵습니다. 오히려 코드를 변경하지 않을 것입니다. 특별히 eval-defun취급 defvar하므로 전체 버퍼와 비슷한 것이 있습니까?
Wilfred Hughes

@WilfredHughes "전체 버퍼에 대한 것"이 무슨 뜻인지 잘 모르겠습니다. makunbound현재 버퍼에서 변수를 선언 한 다음 다시 평가할 단일 함수를 원 하십니까? 당신은 당신 자신의 것을 쓸 수는 있지만, 이것에 대한 기본 기능이 있다고 생각하지 않습니다. 편집 : 걱정하지 마십시오, 당신이 말하는 것을 얻습니다. 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); 그런 다음 sexp C-x C-edefvar; 그때 (default-value 'a). C-x C-e, eval-region,과에 같은 defvarsexp는 할 수 없습니다 기본값을 변경합니다.
Drew
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.