ฉันจะบังคับให้ประเมินค่า defvar อีกครั้งได้อย่างไร


21

สมมติว่าฉันมีบัฟเฟอร์เสียงกระเพื่อม Emacs ที่ประกอบด้วย:

(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รูปแบบ
Wilfred Hughes

คำตอบ:


29

ตามที่อธิบายไว้ในคำตอบอื่น ๆ การประเมิน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))))

5
นี่คือคำตอบ ไม่จำเป็นต้องมี defvars ปลอมหรือ setqs พิเศษ เพียงแค่ใช้แทนeval-defun eval-last-sexp คุณยังสามารถเขียนฟังก์ชันว่าสายในทุกรูปแบบในบัฟเฟอร์และการใช้มันแทนeval-defun eval-buffer
Malabarba

1
@Malabarba โพสต์นี้สั้นเกินไปที่จะตอบคำถาม ใช้eval-defunแทนeval-last-sexpแน่ใจ eval-bufferแต่ความยากลำบากสำหรับ
Gilles 'หยุดชั่วร้าย'

@Gilles ใช่คุณพูดถูก ฉันได้เพิ่มความคิดรวบยอดเบื้องต้นของความคิดของ @ Malabara eval-defunเกี่ยวกับการโทรในทุกรูปแบบระดับบนสุดในบัฟเฟอร์
ffevotte

1
วิธีนี้ดูเหมือนจะไม่ทำงานถ้าไม่ได้อยู่ภายในdefvar defunตัวอย่าง: (progn (defvar foo "bar")).
Kaushal Modi

2
@kaushalmodi ตัวอย่างหลังที่คุณอ้างถึง (ตัวแปรที่จัดเก็บนิพจน์ทั่วไป) มีลักษณะเหมือนตัวเลือกสำหรับผู้สมัครdefconst(ซึ่งจะถูกประเมินอีกครั้ง) เมื่อไม่นานมานี้มีการโพสต์ที่
แจ่มชัด

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 ใหม่ได้ทั่วโลกและควบคุมว่าจะกำหนดค่าของสัญลักษณ์เป็น ARIT-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)รูปแบบซึ่งใช้ในการบอก byte-compiler เกี่ยวกับการดำรงอยู่ของตัวแปรโดยไม่ต้องตั้งค่า
ffevotte

หากคุณจำเป็นอย่างยิ่งที่จะสร้างนิยามใหม่defvarกับแมโครคุณอาจจะดีกว่า prefixing เดิมdefvarรูปแบบที่มีมากกว่าแทนที่โดยmakunbound setq
ffevotte

ใช่มันเป็นความคิดที่แย่มากและควรใช้กับสิ่งต่าง ๆ เช่นการประเมินค่า defvars ของแพ็กเกจที่โหลดใหม่ในบัฟเฟอร์รอยขีดข่วนของคุณเท่านั้นคุณไม่ควรจัดส่งสิ่งนี้
Jordon Biondo

@ ฟรานเชสโก้คุณเป็นคนที่ถูกต้องเกี่ยวกับเวอร์ชัน makunbound ฉันได้ใช้มัน แต่หลงทางจากความคิดฉันวางโค้ดนั้นไว้บนคำตอบของฉันเป็นทางเลือก
Jordon Biondo

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 จริง


ฉันออกไปเล่นกับeval-bufferเสื้อคลุมที่จะผูกทุกอย่างไว้ก่อน แต่คำตอบของ @ Francesco เกี่ยวกับ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เป็นพิเศษดังนั้นมีบางสิ่งที่คล้ายกันสำหรับบัฟเฟอร์ทั้งหมดหรือไม่
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); จากนั้นC-x C-eหลังจากdefvarsexp; (default-value 'a)แล้วก็ C-x C-e, eval-regionและสิ่งที่คล้ายกันในdefvarsexp ไม่เปลี่ยนค่าเริ่มต้น
ดึง
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.