Bagaimana cara saya memaksa evaluasi ulang defvar?

21

Misalkan saya memiliki buffer Emacs lisp yang berisi:

(defvar foo 1)

Jika saya menelepon eval-last-sexpatau eval-buffer, footerikat ke 1. Jika saya mengedit buffer ini ke:

(defvar foo 2)

eval-last-sexpdan eval-bufferjangan mengeksekusi ulang baris ini, jadi foomasih 1.

Ini sangat menantang ketika ada beberapa pernyataan seperti itu dan saya harus melacak garis mana yang tidak dievaluasi kembali.

Saya melihat hanya me-restart Emacs dan kemudian (require 'foo), tetapi kemudian saya harus berhati-hati untuk menghindari memuat file .elc yang lebih tua.

Bagaimana saya bisa benar-benar yakin bahwa variabel dan fungsi yang didefinisikan dalam file saat ini berada dalam keadaan yang sama seperti memuat kode baru dalam contoh Emacs baru?

Wilfred Hughes
sumber
Anda tidak dapat " benar-benar yakin bahwa Emacs berada dalam keadaan yang sama dengan memuat kode baru dalam contoh Emacs baru " tanpa melakukan hal itu. Jika Anda ingin memastikan hanya wrt ini dan variabel global lainnya , maka Anda dapat menghapus nilainya menggunakan makunbounddan kemudian mengevaluasi kembali kode dalam buffer.
Drew
Tentu, efek samping seperti (kode konyol) (incf emacs-major-version)saya dapat hidup dengan terjadi berulang kali. Saya tertarik untuk meretas kode dengan banyak defvarbentuk.
Wilfred Hughes

Jawaban:

29

Seperti yang dijelaskan dalam jawaban lain, mengevaluasi defvarformulir menggunakan eval-last-sexptidak mereset nilai default.

Sebaliknya, Anda dapat menggunakan eval-defun(terikat C-M-xdi emacs-lisp-modesecara default), yang mengimplementasikan perilaku yang Anda inginkan sebagai pengecualian khusus:

Jika pembelotan saat ini sebenarnya adalah panggilan ke defvaratau defcustom, mengevaluasinya dengan cara ini me-reset variabel menggunakan ekspresi nilai awal bahkan jika variabel sudah memiliki beberapa nilai lainnya. (Biasanya defvardan defcustomjangan ubah nilainya jika sudah ada.)


Jika Anda perlu mengevaluasi konten lengkap dari buffer, Anda dapat menulis fungsi yang berjalan di formulir tingkat atas secara bergantian dan memanggil eval-defunmasing-masing. Sesuatu seperti ini seharusnya bekerja:

(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
sumber
5
Ini jawabannya. Tidak perlu untuk defvars palsu atau setq tambahan. Cukup gunakan eval-defunsaja eval-last-sexp . Anda bahkan dapat menulis fungsi yang memanggil eval-defunsetiap formulir di buffer, dan menggunakannya sebagai gantinya eval-buffer.
Malabarba
1
@Malabarba Posting ini jauh dari menjawab pertanyaan. Gunakan eval-defunalih-alih eval-last-sexp, tentu saja, tetapi kesulitannya adalah untuk eval-buffer.
Gilles 'SANGAT berhenti menjadi jahat'
@Gilles ya, kamu benar. Saya menambahkan implementasi sementara dari gagasan @ Malabara tentang memanggil eval-defunsetiap formulir tingkat atas di buffer.
ffevotte
1
Pendekatan ini tampaknya tidak berfungsi jika defvartidak ada dalam defun. Contoh: (progn (defvar foo "bar")).
Kaushal Modi
2
@kaushalmodi Contoh terakhir yang Anda kutip (variabel yang menyimpan ekspresi reguler) sangat mirip dengan kandidat defconst(yang selalu dievaluasi ulang). Baru-baru ini ada posting yang sangat mencerahkan tentang topik ini dalam tanda kurung tanpa akhir
ffevotte
5

Seperti jawaban yang lain katakan, ini hanya cara defvar bekerja, tetapi Anda dapat mengatasinya, ini adalah hal yang paling sulit.

Anda dapat mendefinisikan kembali sementara bagaimana defvar bekerja jika Anda ingin dan selama waktu itu, memuat ulang paket yang ingin Anda atur ulang.

Saya menulis makro di mana selama evaluasi tubuh, nilai-nilai defvars akan selalu dievaluasi kembali.

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

Contoh penggunaan:

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

Catatan: Ini hanya digunakan untuk tujuan mengevaluasi kembali defvars, karena hanya mengabaikan dokumen ketika mengevaluasi ulang. Anda dapat memodifikasi makro untuk mendukung evaluasi ulang yang menerapkan dokumen juga, tapi saya akan menyerahkannya kepada Anda.

Dalam kasus Anda, Anda bisa melakukannya

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

Tapi ketahuilah apa yang dilakukan oleh elisp agar mengharapkan defvar berfungsi seperti yang ditentukan, bisa jadi mereka menggunakan defvar untuk mendefinisikan dan setq dalam beberapa fungsi init untuk menentukan nilainya, sehingga Anda bisa berakhir dengan nil'ing variabel yang tidak Anda inginkan tetapi ini mungkin jarang terjadi.

Implementasi Alternatif

Dengan menggunakan ini, Anda bisa mendefinisikan ulang defvar secara global dan mengontrol apakah itu akan menetapkan nilai simbol ke argumen INIT-VALUE bahkan jika simbol tersebut didefinisikan dengan mengubah nilai defvar-always-reeval-valuessimbol baru .

;; 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))
Jordon Biondo
sumber
1
Saya tidak yakin mendefinisikan kembali perilaku defvaradalah ide yang baik: ada beberapa kemungkinan penggunaan defvar, dengan semantik yang sedikit berbeda. Sebagai contoh, salah satu penggunaan makro Anda tidak memperhitungkan adalah (defvar SYMBOL)formulir, yang digunakan untuk memberitahu byte-compiler tentang keberadaan variabel tanpa menetapkan nilai.
ffevotte
Jika Anda benar-benar perlu mendefinisikan ulang defvardengan makro, Anda mungkin akan lebih baik mengawali defvarbentuk asli dengan makunbound, daripada menggantinya dengan setq.
ffevotte
Ya, itu adalah ide yang mengerikan, dan seharusnya hanya digunakan untuk hal-hal seperti mengevakuasi ulang defvars dari paket yang dimuat di buffer awal Anda, Anda tidak boleh mengirimkan sesuatu seperti ini.
Jordon Biondo
@ Francesco juga Anda benar tentang versi makunbound, saya telah menerapkan itu tetapi menyimpang dari ide, saya telah meletakkan kode itu pada jawaban saya sebagai alternatif.
Jordon Biondo
3

The defvarsedang dievaluasi dan melakukan apa yang telah Anda tentukan. Namun, defvarhanya menetapkan nilai awal:

Argumen opsional INITVALUE dievaluasi, dan digunakan untuk mengatur SYMBOL, hanya jika nilai SYMBOL batal.

Jadi untuk mencapai apa yang Anda inginkan, Anda harus melepaskan ikatan variabel sebelum mengevaluasi ulang, mis

(makunbound 'foo)

atau gunakan setquntuk mengatur nilai, mis

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

Jika Anda tidak perlu menentukan docstring di sini, Anda dapat melewati defvarsemuanya.

Jika Anda benar-benar ingin menggunakan defvardan melepaskan ikatan ini secara otomatis, Anda harus menulis fungsi untuk menemukan defvarpanggilan di buffer saat ini (atau wilayah, atau sexp terakhir, dll); panggilan makunbounduntuk masing-masing; dan kemudian melakukan eval yang sebenarnya.

glukas
sumber
Saya tidak bermain dengan eval-bufferpembungkus yang akan melepaskan ikatan segalanya terlebih dahulu, tetapi jawaban @ Francesco tentang eval-defunbenar-benar apa yang Anda inginkan.
glukas
1

Makro berikut dibuat dengan menelusuri eval-defunfungsi pendukungnya dan memodifikasinya sehingga tidak perlu lagi mengevaluasi wilayah buffer tertentu. Saya membutuhkan bantuan di utas terkait. Mengubah ekspresi menjadi string , dan @Tobias datang untuk menyelamatkan - mengajari saya cara mengubah fungsi tidak sempurna menjadi makro. Saya tidak berpikir kita harus eval-sexp-add-defvarsmendahului elisp--eval-defun-1, tetapi jika seseorang berpikir itu penting, beri tahu saya.

;;; 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))
daftar hukum
sumber
0

Masalahnya bukan bahwa garis tidak dievaluasi kembali. Masalahnya adalah bahwa defvarmendefinisikan suatu variabel dan nilai defaultnya . Jika variabel sudah ada, maka mengubah nilai defaultnya tidak mengubah nilai saat ini. Sayangnya, saya pikir Anda perlu menjalankan setquntuk setiap variabel yang nilainya ingin Anda perbarui.

Ini mungkin berlebihan, tetapi Anda dapat memperbarui file seperti ini jika Anda ingin dapat dengan mudah memperbarui fooke nilai default yang baru.

(defvar foo 2)
(setq foo 2)

tetapi itu mengharuskan Anda mempertahankan nilai default di dua tempat dalam kode Anda. Anda juga bisa melakukan ini:

(makunbound 'foo)
(defvar foo 2)

tetapi jika ada kesempatan yang foodinyatakan di tempat lain Anda mungkin memiliki beberapa efek samping untuk dihadapi.

nispio
sumber
Itu sulit ketika mencoba menguji perubahan ke mode kompleks. Saya lebih suka tidak mengubah kode. eval-defunmemperlakukan defvarsecara khusus, jadi pasti ada sesuatu yang serupa untuk seluruh penyangga?
Wilfred Hughes
@ WilfredHughes Saya tidak yakin apa yang Anda maksud dengan "sesuatu untuk buffer keseluruhan." Anda menginginkan satu fungsi yang akan makunbounddideklarasikan oleh variabel apa pun di buffer saat ini, dan kemudian evaluasi ulang? Anda dapat menulis sendiri, tetapi saya tidak berpikir bahwa ada fungsi out-of-the-box untuk ini. EDIT: Sudahlah, saya mengerti apa yang Anda katakan. Sebuah eval-defunyang berfungsi pada seluruh buffer. Sepertinya @JordonBiondo punya solusi untuk itu.
nispio
Tidak. Masalahnya adalah kurangnya evaluasi ulang: defvartidak melakukan apa-apa jika variabel sudah memiliki nilai (seperti yang dikatakan oleh dok:) The optional argument INITVALUE is evaluated, and used to set SYMBOL, only if SYMBOL's value is void.. Masalahnya bukan yang defvarmengubah nilai default dan bukan nilai saat ini. (defvar a 4) (default-value 'a) (setq a 2) (default-value 'a); kemudian C-x C-esetelah defvarsexp; kemudian (default-value 'a). C-x C-e,, eval-regiondan sejenisnya pada defvarsexp tidak mengubah nilai default.
Drew