Bagaimana cara menulis pembungkus fungsi “pass-through” transparan?

10

Apa yang saya maksud dengan "fungsi bungkus transparan 'pass-through'" adalah fungsi, sebut saja wrapper, yang mengembalikan hasil dari meneruskan semua argumennya ke beberapa fungsi lain, sebut saja wrappee.

Bagaimana ini dilakukan di Emacs Lisp?

NB: Fungsi ideal wrapperadalah agnostik tentang wrappeetanda tangan fungsi; yaitu tidak mengetahui jumlah, posisi, nama, dll. dari wrappeeargumen; itu hanya meneruskan semua argumennya wrappee, seolah-olah wrappeetelah menjadi yang awalnya disebut. (Namun, tidak perlu untuk mengacaukan tumpukan panggilan untuk mengganti panggilan wrapperdengan panggilan ke wrappee.)

Saya mengirim sebagian jawaban untuk pertanyaan saya:

(defun wrapper (&rest args) (apply 'wrappee args))

Ini hanya bekerja jika wrappeeini tidak interaktif. Rupanya, cara fungsi interaktif mendapatkan argumen mereka mewakili "saluran" yang berbeda dari apa yang dicakup oleh (&rest args)mantera. Yang saya masih perlu, oleh karena itu, adalah equally- wrappeemitra -agnostic dari (&rest args)tanda tangan untuk kasus di mana wrappeemerupakan interaktif fungsi.

(Pertanyaan ini dimotivasi oleh masalah yang dijelaskan dalam pertanyaan sebelumnya .)


Dalam hal klarifikasi lebih lanjut tentang apa yang saya minta diperlukan, di bawah ini adalah beberapa contoh, menunjukkan setara dengan Python dan JavaScript dari apa yang saya cari.

Dengan Python beberapa cara standar untuk mengimplementasikan pembungkus seperti itu ditunjukkan di bawah ini:

def wrapper(*args, **kwargs):
    return wrappee(*args, **kwargs)

# or

wrapper = lambda *args, **kwargs: wrappee(*args, **kwargs)

(Di sini *argssingkatan dari "semua argumen posisi", dan **kwargssingkatan dari "semua argumen kata kunci".)

Setara JavaScript akan menjadi seperti ini:

function wrapper () { return wrappee.apply(this, arguments); }

// or

wrapper = function () { return wrappee.apply(this, arguments); }

Sebagai catatan, saya tidak setuju bahwa pertanyaan ini adalah duplikat dari Bagaimana menerapkan mapcar ke suatu fungsi dengan banyak argumen . Saya bingung menjelaskan mengapa, karena kedua pertanyaan itu tampak sangat berbeda bagi saya. Itu seperti ditanya "jelaskan mengapa apel tidak dianggap setara dengan jeruk". Pertanyaan belaka itu sangat gila, sehingga orang ragu akan dapat menemukan jawaban yang akan memuaskan orang yang menanyakannya.

kjo
sumber
Apakah Anda mempertimbangkan untuk menggunakan saran / layanan?
wasamasa
@wasamasa: tidak, dan, lebih lagi, saya gagal melihat bagaimana saran / layanan akan berlaku untuk pertanyaan ini. Bagaimanapun, saya menemukan advicehal - hal yang cukup bermasalah sehingga saya lebih memilih untuk tidak melakukannya. Sebenarnya, motivasi untuk pertanyaan ini adalah mencoba menemukan solusi untuk masalah yang tidak dapat dipecahkan yang saya miliki dengan fungsi yang disarankan ...
kjo
1
@wasamasa: Saran menghadirkan masalah yang sama. Anda dapat menentukan apa yang harus dilakukan dengan salah satu argumen, tetapi untuk membuatnya interaktif, Anda perlu menentukan bagaimana argumen harus disediakan. TKI, Anda perlu memberikan interactivespek.
Drew
1
Yang saya maksudkan adalah menasihati fungsi interaktif asli untuk melakukan apa pun yang Anda inginkan terjadi sebelum dan sesudahnya, dengan cara itu Anda tidak perlu khawatir tentang spesifikasi interaktif.
wasamasa
2
@wasamasa: Ya, tapi itu berbeda. Saran selalu untuk fungsi tertentu , apakah interaktif atau tidak. Dan jika itu adalah perintah maka tidak ada masalah - perilaku interaktifnya diwarisi untuk perintah yang disarankan (kecuali saran tersebut mengubah perilaku interaktif). Pertanyaan ini adalah tentang fungsi / perintah yang sewenang - wenang , bukan yang khusus.
Drew

Jawaban:

11

Tentu saja mungkin termasuk interactivespesifikasinya. Kita berurusan dengan elisp ! (Lisp adalah bahasa di mana konstruk yang paling penting adalah daftar. Formulir yang bisa dipanggil hanya daftar. Jadi, Anda dapat membangunnya setelah Anda sukai.)

Aplikasi: Anda ingin menambahkan beberapa fungsi ke beberapa fungsi secara otomatis. Fungsi yang diperluas harus diberi nama baru sehingga defadvicetidak berlaku.

Pertama, versi yang sesuai dengan tujuan Anda. Kami mengatur sel fungsi ( fset) simbol wrapperdengan semua informasi yang diperlukan dari wrappeedan menambahkan barang-barang tambahan kami.

Ini berfungsi untuk kedua wrappeedefinisi. Versi pertama wrappeeinteraktif adalah yang kedua tidak.

(defun wrappee (num str)
  "Nontrivial wrappee."
  (interactive "nNumber:\nsString:")
  (message "The number is %d.\nThe string is \"%s\"." num str))

(defun wrappee (num str)
  "Noninteractive wrappee."
  (message "The number is %d.\nThe string is \"%s\"." num str))

(fset 'wrapper (list 'lambda
             '(&rest args)
             (concat (documentation 'wrappee t) "\n Wrapper does something more.")
             (interactive-form 'wrappee)
             '(prog1 (apply 'wrappee args)
            (message "Wrapper does something more."))))

Tetapi lebih mudah untuk mendefinisikan makro yang membangun fungsi yang diperluas. Dengan itu kita bahkan dapat menentukan nama fungsi sesudahnya. (Bagus untuk versi otomatis.)

Setelah mengeksekusi kode di bawah ini Anda dapat menelepon secara wrapper-interactiveinteraktif dan wrapper-non-interactivenon-interaktif.

(defmacro make-wrapper (wrappee wrapper)
  "Create a WRAPPER (a symbol) for WRAPPEE (also a symbol)."
  (let ((arglist (make-symbol "arglist")))
  `(defun ,wrapper (&rest ,arglist)
     ,(concat (documentation wrappee) "\n But I do something more.")
     ,(interactive-form wrappee)
     (prog1 (apply (quote ,wrappee) ,arglist)
       (message "Wrapper %S does something more." (quote ,wrapper))))))

(defun wrappee-interactive (num str)
  "That is the doc string of wrappee-interactive."
  (interactive "nNumber:\nsString:")
  (message "The number is %d.\nThe string is \"%s\"." num str))

(defun wrappee-non-interactive (format &rest arglist)
  "That is the doc string of wrappee-non-interactive."
  (apply 'message format arglist))

(make-wrapper wrappee-interactive wrapper-interactive)
(make-wrapper wrappee-non-interactive wrapper-non-interactive)
;; test of the non-interactive part:
(wrapper-non-interactive "Number: %d, String: %s" 1 "test")

Catatan, sampai sekarang saya belum menemukan cara untuk mentransfer formulir pernyataan tetapi itu juga harus dimungkinkan.

Tobias
sumber
2
Hm, ada yang memilih jawaban ini. Saya tidak terlalu peduli dengan skor, tetapi yang saya pedulikan adalah alasan untuk memilihnya. Jika Anda memilih, silakan tinggalkan komentar! Ini akan memberi saya kesempatan untuk meningkatkan jawabannya.
Tobias
Saya tidak tahu pasti, tapi ini pasti membuat orang membaca kode dari paket yang menggunakannya, pergi WTF. Dalam kebanyakan kasus, pilihan yang lebih masuk akal adalah untuk menghadapinya dan menulis fungsi melakukan pembungkusan secara manual (baik dengan menerapkan atau dengan menulis ulang bagian dari spesifikasi interaktif.
wasamasa
2
@wasamasa saya sebagian setuju. Namun demikian, ada kasus di mana instrumentasi otomatis wajib. Contohnya adalah edebug. Selain itu, ada fungsi di mana interactive-spesifikasi jauh lebih besar dari fungsi tubuh. Dalam kasus seperti itu, penulisan ulang interactivespesifikasi bisa sangat membosankan. Pertanyaan dan jawabannya membahas prinsip-prinsip yang disyaratkan.
Tobias
1
Secara pribadi, saya menemukan jawaban ini cukup instruktif, tidak hanya dalam hal ruang lingkup pertanyaan, tetapi juga karena menunjukkan aplikasi makro alami, dan bagaimana satu langkah dari defun ke makro. Terima kasih!
gsl
11

Saya harus memecahkan masalah yang sangat mirip nadvice.el, jadi di sini ada solusi (yang menggunakan beberapa kode dari nadvice.el):

(defun wrapper (&rest args)
  (interactive (advice-eval-interactive-spec
                (cadr (interactive-form #'wrappee))))
  (apply #'wrappee args))

Dibandingkan dengan solusi lain yang diposting sejauh ini, yang satu ini memiliki keuntungan bekerja dengan benar jika wrappeedidefinisikan ulang dengan spesifikasi interaktif yang berbeda (yaitu tidak akan terus menggunakan spesifikasi yang lama).

Tentu saja, jika Anda ingin bungkus Anda benar-benar transparan, Anda bisa melakukannya lebih sederhana:

(defalias 'wrapper #'wrappee)
Stefan
sumber
Ini adalah satu-satunya jawaban yang memungkinkan untuk mendefinisikan pembungkus yang menemukan apa yang dibungkusnya saat runtime. Sebagai contoh, saya ingin menambahkan pintasan yang melakukan tindakan yang ditentukan oleh beberapa perintah yang dicari saat runtime. Dengan menggunakan advice-eval-interactive-specseperti yang disarankan di sini saya dapat membuat spesifikasi interaktif yang sesuai dengan pembungkus dinamis itu.
Igor Bukanov
Apakah mungkin untuk membuat called-interactively-pkembali tdi wrappee? Ada funcall-interactivelytapi tidak adaapply-interactively
clemera
1
@compunaut: Tentu saja, Anda bisa melakukannya (apply #'funcall-interactively #'wrappee args)jika mau. Tetapi Anda hanya boleh melakukannya jika fungsinya disebut secara interaktif, jadi kira-kira seperti itu (apply (if (called-interactively-p 'any) #'funcall-interactively #'funcall) #'wrappee args).
Stefan
Ha, terima kasih! Entah bagaimana tidak bisa berpikir di luar kotak saya.
clemera
1

sunting: Jawaban Tobias lebih bagus dari ini, karena memperoleh bentuk interaktif yang tepat dan dokumentasi dari fungsi yang dibungkus.


Menggabungkan jawaban Aaron Harris dan kjo, Anda dapat menggunakan sesuatu seperti:

(defmacro my-make-wrapper (fn &optional name)
  "Return a wrapper function for FN defined as symbol NAME."
  `(defalias ',(or (eval name)
                   (intern (concat "my-" (symbol-name (eval fn)) "-wrapper")))
     (lambda (&rest args)
       ,(format "Generic wrapper for %s."
                (if (symbolp (eval fn))
                    (concat "`" (symbol-name (eval fn)) "'")
                  fn))
       (interactive)
       (if (called-interactively-p 'any)
           (call-interactively ,fn)
         (apply ,fn args)))))

Pemakaian:

(my-make-wrapper 'find-file 'wrapper-func)

Sebut pembungkus dengan salah satu dari:

(wrapper-func "~/.emacs.d/init.el")

M-x wrapper-func

phils
sumber