Làm thế nào để tôi buộc đánh giá lại một defvar?


21

Giả sử tôi có một bộ đệm lisp Emacs có chứa:

(defvar foo 1)

Nếu tôi gọi eval-last-sexphoặc eval-buffer, foobị ràng buộc thành 1. Nếu tôi sau đó chỉnh sửa bộ đệm này thành:

(defvar foo 2)

eval-last-sexpeval-bufferkhông thực hiện lại dòng này, vì vậy foovẫn là 1.

Điều này đặc biệt khó khăn khi có nhiều tuyên bố như vậy và tôi phải theo dõi những dòng nào không được đánh giá lại.

Tôi đã xem xét chỉ khởi động lại Emacs và sau đó (require 'foo), nhưng sau đó tôi phải cẩn thận để tránh tải bất kỳ tệp .elc cũ nào.

Làm thế nào tôi có thể hoàn toàn chắc chắn, tích cực chắc chắn rằng các biến và hàm được xác định trong tệp hiện tại ở cùng trạng thái với việc tải mã trong một trường hợp Emacs mới?


Bạn không thể " hoàn toàn chắc chắn, tích cực chắc chắn rằng Emacs đang ở trạng thái giống như đang tải mã trong một trường hợp Emacs mới " mà không làm như vậy. Nếu bạn muốn chắc chắn chỉ ghi lại biến này và các biến toàn cục khác , thì bạn có thể xóa giá trị của chúng bằng cách sử dụng makunboundvà sau đó đánh giá lại mã trong bộ đệm.
vẽ

Chắc chắn, tác dụng phụ như (mã ngớ ngẩn) (incf emacs-major-version)tôi có thể sống với xảy ra lặp đi lặp lại. Tôi quan tâm đến việc hack mã với rất nhiều defvarhình thức.
Wilfred Hughes

Câu trả lời:


29

Như đã giải thích trong các câu trả lời khác, việc đánh giá một defvarbiểu mẫu bằng cách sử dụng eval-last-sexpkhông đặt lại giá trị mặc định.

Thay vào đó, bạn có thể sử dụng eval-defun(ràng buộc để C-M-xemacs-lisp-modemặc định), mà thực hiện hành vi mà bạn muốn như một ngoại lệ đặc biệt:

Nếu defun hiện tại thực sự là một lệnh gọi defvarhoặc defcustom, đánh giá nó theo cách này đặt lại biến bằng cách sử dụng biểu thức giá trị ban đầu của nó ngay cả khi biến đã có một số giá trị khác. (Thông thường defvardefcustomkhông thay đổi giá trị nếu đã có.)


Nếu bạn cần đánh giá toàn bộ nội dung của bộ đệm, bạn có thể viết một hàm lần lượt chuyển các biểu mẫu cấp cao nhất và gọi eval-defuntừng hàm. Một cái gì đó như thế này sẽ hoạt động:

(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
Đây là câu trả lời. Không cần defvars giả hoặc setqs thêm. Chỉ cần sử dụng eval-defunthay vì eval-last-sexp . Bạn thậm chí có thể viết một hàm gọi eval-defuntrên mọi biểu mẫu trong bộ đệm và sử dụng nó thay vì eval-buffer.
Malabarba

1
@Malabarba Bài đăng này không thể trả lời được câu hỏi. Sử dụng eval-defunthay vì eval-last-sexp, chắc chắn, nhưng khó khăn là cho eval-buffer.
Gilles 'SO- ngừng trở nên xấu xa'

@Gilles đúng, bạn đúng. Tôi đã thêm một triển khai dự kiến ​​về ý tưởng của @ Malabara về việc gọi eval-defunmọi hình thức cấp cao nhất trong bộ đệm.
ffevotte

1
Cách tiếp cận này dường như không hiệu quả nếu defvarkhông nằm trong a defun. Ví dụ : (progn (defvar foo "bar")).
Kaushal Modi

2
@kaushalmodi Các ví dụ sau mà bạn trích dẫn (các biến lưu trữ biểu thức chính quy) trông rất giống các ứng cử viên defconst(luôn được đánh giá lại). Gần đây có một bài viết rất khai sáng về chủ đề này trong ngoặc đơn vô tận
ffevotte 13/03/2015

5

Giống như các câu trả lời khác nói, đây chỉ là cách defvar hoạt động, nhưng bạn có thể vượt qua nó, đây là điều tất yếu.

Bạn có thể tạm thời xác định lại cách thức hoạt động của defvar nếu bạn muốn và trong thời gian đó, tải lại các gói bạn muốn đặt lại.

Tôi đã viết một macro trong đó trong quá trình đánh giá cơ thể, các giá trị của defvars sẽ luôn được đánh giá lại.

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

Cách sử dụng ví dụ:

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

Lưu ý: Điều này chỉ nên được sử dụng cho mục đích đánh giá lại các defvars, vì nó chỉ bỏ qua các tài liệu khi đánh giá lại. Bạn có thể sửa đổi macro để hỗ trợ đánh giá lại cũng áp dụng các tài liệu, nhưng tôi sẽ để nó tùy thuộc vào bạn.

Trong trường hợp của bạn, bạn có thể làm

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

Nhưng hãy biết những người viết elisp làm gì để mong đợi defvar hoạt động như được chỉ định, có thể họ sử dụng defvar để định nghĩa và setq trong một số hàm init để chỉ định giá trị, vì vậy bạn có thể kết thúc các biến bạn không có ý định nhưng điều này có lẽ là hiếm

Thực hiện thay thế

Sử dụng điều này, bạn có thể xác định lại defvar trên toàn cầu và kiểm soát xem nó có đặt giá trị của biểu tượng thành đối số INIT-VALUE ngay cả khi biểu tượng được xác định bằng cách thay đổi giá trị của defvar-always-reeval-valuesbiểu tượng mới hay không .

;; 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
Tôi không chắc chắn xác định lại hành vi của defvarmột ý tưởng hay: có một số cách sử dụng khác nhau có thể defvar, với ngữ nghĩa hơi khác nhau. Ví dụ, một sử dụng macro của bạn không chiếm tài khoản là (defvar SYMBOL)biểu mẫu, được sử dụng để báo cho trình biên dịch byte về sự tồn tại của một biến mà không đặt giá trị.
ffevotte

Nếu bạn thực sự cần xác định lại defvarbằng một macro, có lẽ tốt hơn là bạn nên thêm tiền tố vào defvarbiểu mẫu gốc bằng một makunbound, thay vì thay thế nó bằng setq.
ffevotte

Vâng, đó là một ý tưởng tồi tệ, và chỉ nên được sử dụng cho những việc như tính toán lại các gói của gói được tải trong bộ đệm đầu của bạn, bạn không bao giờ nên gửi một cái gì đó như thế này.
Jordon Biondo

@Francesco bạn cũng đúng về phiên bản makunbound, tôi đã thực hiện điều đó nhưng đi lạc khỏi ý tưởng, tôi đã đặt mã đó vào câu trả lời của mình như một cách thay thế.
Jordon Biondo

3

Việc defvarnày đang được đánh giá và thực hiện chính xác những gì bạn đã chỉ định. Tuy nhiên, defvarchỉ đặt giá trị ban đầu:

Đối số tùy chọn INITVALUE được ước tính và được sử dụng để đặt SYMBOL, chỉ khi giá trị của SYMBOL bị vô hiệu.

Vì vậy, để đạt được những gì bạn muốn, bạn cần phải hủy liên kết biến trước khi đánh giá lại, vd

(makunbound 'foo)

hoặc sử dụng setqđể đặt giá trị, ví dụ:

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

Nếu bạn không cần chỉ định một chuỗi doc ở đây, bạn có thể bỏ qua defvarhoàn toàn.

Nếu bạn thực sự muốn sử dụng defvarvà tự động hủy liên kết này, bạn sẽ cần phải viết một hàm để tìm defvarcác cuộc gọi trong bộ đệm hiện tại (hoặc vùng, hoặc sexp cuối, v.v.); gọi makunboundcho từng người; và sau đó làm eval thực tế.


Tôi đã nghỉ chơi với một eval-buffertrình bao bọc sẽ hủy kết nối mọi thứ trước tiên, nhưng câu trả lời của @ Francesco eval-defunthực sự là những gì bạn muốn.
glucas

1

Macro sau đây được tạo bằng cách truy tìm eval-defuncác chức năng hỗ trợ của nó và sửa đổi nó để không còn cần thiết phải đánh giá một vùng của một bộ đệm cụ thể. Tôi cần trợ giúp trong chuỗi liên quan Chuyển đổi biểu thức thành chuỗi và @Tobias đã đến cứu - dạy tôi cách chuyển đổi hàm không hoàn hảo thành macro. Tôi không nghĩ rằng chúng ta cần eval-sexp-add-defvarsphải đi trước elisp--eval-defun-1, nhưng nếu ai đó nghĩ rằng điều đó là quan trọng, xin vui lòng cho tôi biết.

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

Vấn đề không phải là dòng không được đánh giá lại. Vấn đề là defvarxác định một biến và giá trị mặc định của nó . Nếu một biến đã tồn tại, thì việc thay đổi giá trị mặc định của nó không làm thay đổi giá trị hiện tại. Thật không may, tôi nghĩ bạn sẽ cần chạy một setqbiến cho mọi biến có giá trị bạn muốn cập nhật.

Điều này có thể là quá mức cần thiết, nhưng bạn có thể cập nhật tệp của mình như thế này nếu bạn muốn có thể dễ dàng cập nhật foolên giá trị mặc định mới của nó.

(defvar foo 2)
(setq foo 2)

nhưng điều đó đòi hỏi bạn phải duy trì giá trị mặc định ở hai vị trí trong mã của mình. Bạn cũng có thể làm điều này:

(makunbound 'foo)
(defvar foo 2)

nhưng nếu có một cơ hội foođược tuyên bố ở nơi khác, bạn có thể có một số tác dụng phụ để giải quyết.


Điều đó thật khó khăn khi thử kiểm tra các thay đổi ở chế độ phức tạp. Tôi không muốn thay đổi mã. eval-defunđối xử defvarđặc biệt, vì vậy chắc chắn có một cái gì đó tương tự cho toàn bộ bộ đệm?
Wilfred Hughes

@WilfredHughes Tôi không chắc ý của bạn là "cái gì đó cho toàn bộ bộ đệm". Bạn muốn một hàm duy nhất sẽ makunboundcó bất kỳ biến nào được khai báo trong bộ đệm hiện tại, sau đó đánh giá lại nó? Bạn có thể tự viết, nhưng tôi không nghĩ rằng có một chức năng vượt trội cho việc này. EDIT: Đừng bận tâm, tôi hiểu những gì bạn đang nói. Một eval-defunhoạt động trên toàn bộ bộ đệm. Có vẻ như @JordonBiondo có giải pháp của bạn cho điều đó.
nispio

Không. Vấn đề thiếu đánh giá lại: defvarkhông làm gì nếu biến đã có giá trị (như tài liệu của nó nói The optional argument INITVALUE is evaluated, and used to set SYMBOL, only if SYMBOL's value is void.:). Vấn đề không phảidefvarthay đổi giá trị mặc định và không phải giá trị hiện tại. (defvar a 4) (default-value 'a) (setq a 2) (default-value 'a); sau đó C-x C-edefvarsexp; sau đó (default-value 'a). C-x C-e, eval-regionvà tương tự trên defvarsexp không thay đổi giá trị mặc định.
vẽ
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.