Bagaimana cara memanipulasi daftar argumen di nadvice.el?

12

Sebagai lanjutan dari jawaban untuk pertanyaan lain tentang sistem saran baru :

Dalam gaya lama advice.el, dimungkinkan untuk memanipulasi anggota individu dari daftar argumen fungsi yang disarankan, tanpa membuat pernyataan mengenai anggota yang tidak dimanipulasi. Misalnya, saran berikut:

(defadvice ansi-term (around prompt-for-name last)
  (let ((name (read-from-minibuffer "Tag: ")))
    (and (not (string= name ""))
         (ad-set-arg 1 (concat "Term: " name)))
    ad-do-it))

memungkinkan ketentuan (opsional) argumen nama penyangga untuk ansi-termpanggilan, sementara ansi-termmasih akan mendapatkan argumen pertamanya dengan mendorong sesuai dengan bentuk interaktifnya sendiri.

(Untuk referensi di kemudian hari, ansi-termtandatangannya adalah (PROGRAM &optional BUFFER-NAME), dan formulir interaktifnya meminta PROGRAM dengan beberapa kemungkinan default, tetapi tidak melakukan apa pun mengenai BUFFER-NAME.)

Saya tidak yakin apakah ini mungkin masuk atau tidak nadvice.el. Jika ya, saya tidak yakin bagaimana hal itu dapat dilakukan. Saya telah menemukan beberapa cara untuk mengganti daftar argumen fungsi yang disarankan.

Misalnya, dari * info * (elisp) Kombinator saran :

`:filter-args'
 Call FUNCTION first and use the result (which should be a list) as
 the new arguments to pass to the old function.  More specifically,
 the composition of the two functions behaves like:
      (lambda (&rest r) (apply OLDFUN (funcall FUNCTION r)))

Combinator lain memberikan kemampuan yang serupa, dan utas umum di antara mereka adalah bahwa, sementara daftar argumen fungsi dapat diganti, dipotong, diperluas, dan lain-lain, tidak ada cara yang jelas bagi saran fungsi untuk memodifikasi argumen pada posisi tertentu dalam daftar tanpa menegaskan apa pun tentang sisanya .

Dalam kasus yang sedang dibahas, tampaknya tidak mungkin bagi penulis saran untuk memberikan ansi-termhanya nama penyangga, karena tidak mungkin untuk membuat daftar yang memiliki nilai di posisi 1 tetapi tidak ada, bahkan tidak nil, di posisi 0. Dalam kasus umum, Tampaknya mustahil bagi penulis saran untuk secara sewenang-wenang mengubah argumen di luar posisi 0.

Ini sepertinya disayangkan bahwa, untuk menghasilkan efek yang serupa, perlu menyalin kode tempel: secara spesifik, saya dapat menyalin ansi-termformulir interaktif dan memperluasnya sesuai selera saya, atau saya dapat menyalin ansi-termsemuanya dan memperluasnya juga. Dalam kedua kasus tersebut, sekarang saya harus mendefinisikan kembali bagian dari distribusi Emacs Lisp dalam file init saya, yang menurut saya tidak diinginkan dalam hal daya tahan dan estetika.

Pertanyaan saya adalah: Dapatkah daftar argumen semacam ini diselesaikan nadvice.el? Jika ya, bagaimana caranya?

Aaron Miller
sumber
3
Mengapa Anda tidak mendefinisikan perintah interaktif Anda sendiri di atas istilah ansi? Saya pikir itu solusi yang lebih baik di sini.
lunaryorn
1
Tentu saja tidak ada yang menghentikan saya dari melakukan itu, tetapi itu akan memerlukan penggantian bagian yang lebih baik dari memori otot selama satu dekade, yang ingin saya hindari jika saya bisa.
Aaron Miller

Jawaban:

5

Ini sepertinya disayangkan bahwa, untuk menghasilkan efek yang sama, perlu menyalin-menempelkan kode: [...] Saya dapat menyalin ansi-termformulir interaktif

Sebaliknya, saya pikir itu akan menjadi ide yang baik untuk menyalin-tempel bentuk interaktif dari fungsi yang disarankan, meskipun Anda tidak benar-benar harus melakukannya di sini.

Saya membaca pertanyaan Anda dari atas ke bawah. Ketika saya sampai pada blok-kode saya menduga bahwa saran Anda mungkin mengubah nama buffer. Tapi saya tidak tahu sampai Anda kemudian memberikan tanda tangan sebagai komentar.

Dalam kasus yang sedang dibahas, tampaknya tidak mungkin bagi penulis saran untuk ansi-termhanya memberikan nama penyangga, karena tidak mungkin untuk membuat daftar yang memiliki nilai di posisi 1 tetapi tidak ada, bahkan tidak nil, di posisi 0.

Memang tidak ada yang kurang dari tidak sama sekali. :-) Tapi itu tidak relevan di sini.

Seperti yang Anda lihat dalam dokumentasi yang Anda kutip, nilai yang dikembalikan oleh saran tersebut digunakan sebagai argumen untuk fungsi yang disarankan. Nilai balik harus berupa daftar semua argumen, bukan hanya argumen yang telah berubah.

Tetap sedekat mungkin dengan saran lama, inilah yang harus Anda lakukan menggunakan nadvice:

(defun ansi-term--tag-buffer (args)
  ;; As npostavs pointed out we also have to make sure the list is
  ;; two elements long.  Which makes this approach even more undesirable.
  (when (= (length args) 1)
    (setq args (nconc args (list nil))))
  (let ((name (read-from-minibuffer "Tag: ")))
    (and (not (string= name ""))
         (setf (nth 1 args) (concat "Term: " name))))
  args)

(advice-add 'ansi-term :filter-args 'ansi-term--tag-buffer)

Tapi saya sarankan Anda mendefinisikan saran seperti ini sebagai gantinya:

(defun ansi-term--tag-buffer (program &optional buffer-name)
  (list program
        (let ((tag (read-from-minibuffer "Tag: ")))
          (if (string= tag "")
              buffer-name
            (concat "Term: " tag)))))

Varian ini sebenarnya cukup jelas.

tarsius
sumber
Untuk varian pertama, Anda perlu memperpanjang argsdaftar jika ada panggilan seperti (ansi-term "foo"), atau jika tidak (setf (nth 1 args)...akan menimbulkan kesalahan.
npostav
Ya kau benar. Alasan lain untuk menggunakan varian kedua - yang pertama memiliki bug ;-) Mari, untuk tujuan demonstrasi, anggap saja itu buffer-namewajib.
tarsius
"Sebaliknya, saya pikir itu akan menjadi ide yang baik untuk menyalin-tempel bentuk interaktif dari fungsi yang disarankan" - mengapa begitu? Kode copy-paste adalah ide yang buruk di hampir setiap kasus lainnya; Kenapa tidak disini?
Aaron Miller
Sebenarnya saya tidak berpikir "copy-paste" adalah istilah yang tepat dalam kasus ini, saya hanya menggunakannya karena Anda melakukannya. Tetapi bahkan jika itu tepat untuk menggunakan istilah itu di sini, maka "jangan salin-tempel" hanyalah heuristik bukan aturan mutlak. Heuristik lainnya, yang saya pikir do berlaku di sini, adalah "memberikan nama-nama bermakna dengan variabel dan argumen" dan "ketika Anda memiliki pilihan antara rumit sesuatu atau menjadi verbose, pergi dengan verbose".
tarsius
1
Um, sebenarnya, ini masih rusak, :filter-argssaran mendapat argumen tunggal yang merupakan daftar argumen untuk fungsi yang disarankan, sehingga varian 1 harus turun &restdan varian 2 harus menggunakan semacam konstruksi yang merusak untuk mendapatkan nama yang bagus.
npostavs
3

Begini cara saya melakukannya:

(defun my-ansi-term-prompt-for-name (orig-fun program
                                     &optional buffer-name &rest args)
  (apply orig-fun program
         (or buffer-name
             (let ((name (read-string "Tag: ")))
               (and (> (length name) 0)
                    (concat "Term: " name))))
         args))
(advice-add 'ansi-term :around #'my-ansi-term-prompt-for-name)

sementara saya adalah orang yang memperkenalkan :filter-argssaya pribadi merasa jarang nyaman.

Stefan
sumber