Şeffaf bir “düz geçiş” işlev sargısı nasıl yazılır?


10

"Şeffaf" düz geçişli "işlev sarmalayıcı" ile kastettiğim, bir işlevdir, diyelim wrapperki, tüm argümanını başka bir işleve geçirmekten sonuç döndürür, diyelim wrappee.

Emacs Lisp'de bu nasıl yapılır?

Dikkat: İdeal wrapperişlev, işlevin imzası hakkında agnostiktirwrappee ; yani wrappeeargümanlarının sayısı, pozisyonları, isimleri vb. hakkında hiçbir şey bilmiyor ; tüm argümanlarını wrappeetıpkı wrappeebaşlangıçta çağrılmış gibi geçirir . (Ancak, çağrıyı yerine wrapperbir çağrıyla değiştirmek için çağrı yığınıyla uğraşmanıza gerek yoktur wrappee.)

Soruma kısmi bir cevap gönderdim :

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

Bu sadece çalışır wrappeeolduğunu değil interaktif. Görünüşe göre, etkileşimli işlevlerin argümanlarını alma şekli, (&rest args)büyünün kapsadığı şeyden farklı bir "kanalı" temsil eder . Bu yüzden hala ihtiyacım olan şey, etkileşimli bir işlevin olduğu durumun imzasının eşit wrappeederecede agnostik bir karşılığıdır .(&rest args)wrappee

(Bu soru, bu önceki soruda açıklanan bir sorunla motive edildi .)


İstediğim şeyin daha fazla açıklanması gerekirse, aşağıda Python ve JavaScript eşdeğerlerini gösteren birkaç örnek verilmiştir.

Python'da böyle bir ambalajı uygulamak için birkaç standart yol aşağıda gösterilmiştir:

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

# or

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

(Burada *args"tüm konumsal bağımsız değişkenler", **kwargs"tüm anahtar kelime bağımsız değişkenleri" anlamına gelir.)

JavaScript eşdeğeri şuna benzer:

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

// or

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

Kayıt için, bu sorunun Mapcar'ı birden çok argümanı olan bir işleve nasıl uygulayacağının bir kopyası olduğunu kabul etmiyorum . Nedenini açıklamakta zorlanıyorum, çünkü iki soru benim için çok farklı görünüyor. "Bir elmanın neden bir portakal ile eşdeğer sayılmaması gerektiğini açıklamak" gibi bir soru soruluyor. Sadece o kadar çılgınca bir soru ki, kişinin soran kişiyi tatmin edecek bir cevap bulabileceğinden şüphe ediyor.


Tavsiye / nadvice kullanmayı düşündünüz mü?
wasamasa

@wasamasa: hayır ve dahası, tavsiye / nadvice'nin bu soruya nasıl uygulanacağını göremiyorum. Her durumda, adviceonlardan uzak durmayı tercih edeceğim kadar sorunlu buluyorum . Aslında, bu sorunun motivasyonu tavsiye edilen bir işlevle karşılaştığım başka türlü zor bir soruna bir çözüm bulmaya çalışıyordu ...
kjo

1
@wasamasa: Tavsiye aynı sorunu sunar. Herhangi bir argümanla ne yapacağını söyleyebilirsiniz, ancak etkileşimli hale getirmek için argümanların nasıl sağlanacağını belirtmeniz gerekir. IOW, bir interactiveşartname sağlamanız gerekiyor .
Drew

1
Demek istediğim, orijinal interaktif fonksiyona önce ve sonra ne yapmak istediğinizi yapmasını tavsiye etmek, bu şekilde interaktif özellik hakkında endişelenmenize gerek yok.
wasamasa

2
@wasamasa: Evet, ama bu farklı. Tavsiye her zaman etkileşimli olsun olmasın belirli bir işlev içindir. Ve bu bir komutsa, sorun yoktur - etkileşimli davranışı önerilen komut için miras alınır (tavsiye etkileşimli davranışı yeniden tanımlamazsa). Bu soru, belirli bir işlevle değil, rastgele bir işlev / komutla ilgilidir.
Drew

Yanıtlar:


11

Tabii ki interactiveşartname dahil etmek mümkündür . Burada elisp ile uğraşıyoruz ! (Lisp, en önemli yapıların listeler olduğu dildir. Çağrılabilir formlar sadece listelerdir. Böylece istediğiniz gibi yapılandırabilirsiniz.)

Uygulama: Bazı işlevlere otomatik olarak bazı işlevler eklemek istiyorsunuz. Genişletilmiş işlevlere defadviceuygulanamayacak yeni adlar verilmelidir .

İlk önce tam olarak amacınıza uyan bir versiyon. fsetSembolün işlev hücresini ( ) wrappergerekli tüm bilgilerle ayarlıyoruz ve wrappeeekstra öğelerimizi ekliyoruz.

Her iki wrappeetanım için de çalışır . İlk sürümü wrappeeinteraktif, ikincisi değil.

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

Ancak, genişletilmiş işlevleri oluşturan bir makro tanımlamak daha uygundur. Bundan sonra işlev adlarını bile belirtebiliriz. (Otomatik sürüm için iyidir.)

Aşağıdaki kodu yürüttükten sonra wrapper-interactiveetkileşimli ve etkileşimli wrapper-non-interactiveolmayan çağrı yapabilirsiniz .

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

Şimdiye kadar, deklarasyon formlarını aktarmak için henüz bir yol bulamadım, ancak bu da mümkün olmalı.


2
Hm, birisi bu cevabı düşürdü. Skoru gerçekten umursamıyorum ama umursadığım cevap aşağı oylamanın sebebidir. Aşağı oy kullanırsanız, lütfen bir yorum bırakın! Bu bana cevabı geliştirmem için bir şans verecektir.
Tobias

Emin değilim, ama bu herkes WTF gitmek kullanarak bir paketin kodunu okuma yapmak zorunda. Çoğu durumda, daha mantıklı seçenek onunla başa çıkmak ve ambalajı elle yapan bir işlev yazmaktır (uygula veya etkileşimli spesifikasyonun bölümlerini yeniden yazarak.
wasamasa

2
@wasamasa kısmen katılıyorum. Bununla birlikte, otomatik enstrümantasyonun zorunlu olduğu durumlar vardır. Bir örnek edebug. Ayrıca, interactive-belirtiminin işlev gövdesinden önemli ölçüde daha büyük olduğu işlevler vardır. Bu gibi durumlarda, interactivespesifikasyonun yeniden yazılması oldukça sıkıcı olabilir. Soru ve cevap gerekli ilkeleri ele almaktadır.
Tobias

1
Şahsen, bu sorunun sadece öğretinin kapsamı açısından değil, aynı zamanda makroların doğal bir uygulamasını ve defundan makroya nasıl adım atıldığı konusunda oldukça öğretici buluyorum. Teşekkürler!
gsl

11

Ben çok benzer bir sorunu çözmek zorunda kaldı nadvice.el, bu yüzden burada bir çözüm (hangi nadvice.el kodundan bazılarını kullanır):

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

Şimdiye kadar yayınlanan diğer çözümlerle karşılaştırıldığında, wrappeefarklı bir etkileşimli özellikle yeniden tanımlanırsa (yani eski spesifikasyonu kullanmaya devam etmez) , bu doğru çalışma avantajına sahiptir .

Tabii ki, ambalajınızın gerçekten şeffaf olmasını istiyorsanız, daha basit bir şekilde yapabilirsiniz:

(defalias 'wrapper #'wrappee)

Bu, çalışma zamanında ne saran bir sarıcı tanımlamaya izin veren tek yanıttır. Örneğin, çalışma zamanında aranan bazı komutlar tarafından tanımlanan bir eylem gerçekleştiren bir kısayol eklemek istiyorum. advice-eval-interactive-specBurada önerildiği gibi kullanarak , o dinamik sarmalayıcıya karşılık gelen etkileşimli özellikleri oluşturabilirim.
Igor Bukanov

Bunu yapmak mümkün mü called-interactively-pdönmek tde wrappee? Orada funcall-interactivelyama hayırapply-interactively
clemera

1
@ compunaut: Tabii ki isterseniz de yapabilirsiniz (apply #'funcall-interactively #'wrappee args). Ama bunu sadece işlev interaktif olarak çağrıldığında yapmalısınız, yani böyle bir şey (apply (if (called-interactively-p 'any) #'funcall-interactively #'funcall) #'wrappee args).
Stefan

Ha, teşekkürler! Bir şekilde kutumun dışında düşünemedim.
clemera

1

edit: Tobias'ın cevabı, tam etkileşimli formu ve sarılmış fonksiyonun öğretisini elde ettiği için bundan daha güzel.


Aaron Harris ve kjo'nun cevaplarını birleştirerek, şöyle bir şey kullanabilirsiniz:

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

Kullanımı:

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

Aşağıdakilerden birini içeren çağrı sarmalayıcısı:

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

M-x wrapper-func

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.