จะเขียนตัวห่อหุ้มฟังก์ชั่น“ pass-through” แบบโปร่งใสได้อย่างไร?


10

สิ่งที่ฉันหมายความโดย "โปร่งใส 'ผ่าน' ฟังก์ชั่นเสื้อคลุม" คือฟังก์ชั่นขอเรียกมันwrapperว่าผลตอบแทนที่เป็นผลมาจากการส่งผ่านอาร์กิวเมนต์ทุกฟังก์ชั่นอื่น ๆ wrappeeบางอย่างที่ขอเรียกว่า

วิธีนี้ทำได้ใน Emacs Lisp

หมายเหตุ: wrapperฟังก์ชั่นในอุดมคตินั้นไม่เชื่อเรื่องพระเจ้าเกี่ยวกับwrappeeลายเซ็นของฟังก์ชัน กล่าวคือไม่ทราบจำนวนตำแหน่งตำแหน่ง ฯลฯ ของwrappeeข้อโต้แย้ง มันเพิ่งผ่านการขัดแย้งทั้งหมดของมันไปwrappeeเช่นเดียวกับที่wrappeeได้รับการเรียกเดิม (ไม่จำเป็นต้องยุ่งกับ call stack เพื่อแทนที่ call to wrapperด้วย call to wrappee)

ฉันโพสต์คำตอบบางส่วนสำหรับคำถามของฉัน:

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

นี้จะทำงานเฉพาะเมื่อwrappeeเป็นไม่โต้ตอบ เห็นได้ชัดว่าวิธีการที่ฟังก์ชั่นโต้ตอบได้รับข้อโต้แย้งของพวกเขาแสดงให้เห็นถึง "ช่องทาง" ที่แตกต่างจากสิ่งที่ครอบคลุมโดย(&rest args)คาถา สิ่งที่ฉันยังต้องการคือwrappeeคู่ที่เท่าเทียมกันของ(&rest args)ลายเซ็นสำหรับกรณีที่wrappeeมีฟังก์ชั่นแบบโต้ตอบ

(คำถามนี้เกิดจากปัญหาที่อธิบายไว้ในคำถามก่อนหน้านี้ )


ในกรณีที่ต้องการคำชี้แจงเพิ่มเติมเกี่ยวกับสิ่งที่ฉันขอเป็นด้านล่างเป็นตัวอย่างสองประการแสดง Python และ JavaScript ที่เทียบเท่ากับสิ่งที่ฉันต้องการ

ใน Python สองสามวิธีมาตรฐานในการใช้ wrapper ดังแสดงด้านล่าง:

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

# or

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

(นี่*argsหมายถึง "อาร์กิวเมนต์ตำแหน่งทั้งหมด" และ**kwargsหมายถึง "อาร์กิวเมนต์คำหลักทั้งหมด")

จาวาสคริปต์ที่เทียบเท่าจะเป็นดังนี้:

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

// or

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

สำหรับบันทึกฉันไม่เห็นด้วยว่าคำถามนี้ซ้ำกับวิธีใช้ mapcar กับฟังก์ชันที่มีอาร์กิวเมนต์หลายตัว ฉันสูญเสียที่จะอธิบายว่าทำไมเพราะทั้งสองคำถามดูแตกต่างจากฉันอย่างเห็นได้ชัด มันเหมือนถูกถามว่า "อธิบายว่าทำไมแอปเปิ้ลไม่ควรพิจารณาเทียบเท่ากับส้ม" คำถามเพียงอย่างเดียวก็บ้าไปหมดแล้วมีใครสงสัยว่าจะมีคำตอบที่จะตอบสนองคนที่ถาม


คุณพิจารณาใช้คำแนะนำ / ข้อได้เปรียบหรือไม่?
wasamasa

@wasamasa: ไม่และยิ่งกว่านั้นฉันไม่สามารถดูว่าคำแนะนำ / ข้อได้เปรียบจะนำไปใช้กับคำถามนี้ได้อย่างไร ไม่ว่าในกรณีใดฉันพบadviceสิ่งที่เป็นปัญหามากพอที่ฉันจะค่อนข้างชัดเจน ในความเป็นจริงแรงจูงใจสำหรับคำถามนี้ก็พยายามที่จะหาวิธีการแก้ปัญหาที่ยากเป็นอย่างอื่นที่ฉันมีกับฟังก์ชั่นแนะนำเป็นที่ ...
kjo

1
@wasamasa: คำแนะนำนำเสนอปัญหาเดียวกัน คุณสามารถบอกได้ว่าจะทำอย่างไรกับ args ใด ๆ แต่เพื่อให้เป็นแบบโต้ตอบคุณต้องระบุวิธีที่จะให้ข้อโต้แย้ง IOW คุณต้องให้interactiveข้อมูลจำเพาะ
Drew

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

2
@wasamasa: ใช่ แต่มันแตกต่างกัน คำแนะนำอยู่เสมอสำหรับฟังก์ชั่นเฉพาะไม่ว่าจะโต้ตอบหรือไม่ และถ้ามันเป็นคำสั่งก็ไม่มีปัญหา - พฤติกรรมการโต้ตอบของมันจะสืบทอดมาสำหรับคำสั่งที่แนะนำ (เว้นแต่คำแนะนำจะนิยามพฤติกรรมการโต้ตอบใหม่) คำถามนี้เกี่ยวกับฟังก์ชั่น / คำสั่งโดยพลการไม่ใช่คำถามเฉพาะ
Drew

คำตอบ:


11

แน่นอนมันเป็นไปได้รวมถึงinteractiveข้อกำหนด เรากำลังติดต่อกับelispที่นี่! (เสียงกระเพื่อมเป็นภาษาที่โครงสร้างที่สำคัญที่สุดคือรายการรูปแบบ Callable เป็นเพียงรายการดังนั้นคุณสามารถสร้างได้หลังจากที่คุณชอบ)

แอปพลิเคชัน: คุณต้องการเพิ่มฟังก์ชั่นบางอย่างให้กับฟังก์ชั่นบางอย่างโดยอัตโนมัติ ฟังก์ชันเพิ่มเติมควรได้รับชื่อใหม่ดังนั้นจึงdefadviceไม่สามารถใช้งานได้

รุ่นแรกที่เหมาะกับวัตถุประสงค์ของคุณมากทีเดียว เราตั้งค่าฟังก์ชันเซลล์ ( fset) ของสัญลักษณ์wrapperพร้อมข้อมูลที่จำเป็นทั้งหมดจากwrappeeและเพิ่มสิ่งพิเศษของเรา

มันทำงานได้ทั้งwrappeeคำจำกัดความ รุ่นแรกของwrappeeการโต้ตอบที่สองคือไม่

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

แต่จะสะดวกกว่าในการกำหนดแมโครที่สร้างฟังก์ชันเพิ่มเติม เราสามารถระบุชื่อฟังก์ชันได้ในภายหลัง (ดีสำหรับรุ่นอัตโนมัติ)

หลังจากรันโค้ดด้านล่างคุณสามารถโทรหาwrapper-interactiveแบบโต้ตอบและwrapper-non-interactiveไม่โต้ตอบ

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

หมายเหตุถึงตอนนี้ฉันยังไม่พบวิธีการโอนแบบฟอร์มประกาศ แต่นั่นควรจะเป็นไปได้


2
หืมมีคนลงคะแนนให้กับคำตอบนี้ ฉันไม่สนใจเกี่ยวกับคะแนน แต่สิ่งที่ฉันสนใจคือเหตุผลในการโหวตคำตอบ หากคุณลงคะแนนโปรดแสดงความคิดเห็น! นี่จะให้โอกาสฉันในการปรับปรุงคำตอบ
โทเบียส

ฉันไม่รู้แน่ชัด แต่นี่เป็นข้อผูกมัดที่ทำให้ทุกคนอ่านโค้ดของแพ็คเกจที่ใช้มันไป WTF ในกรณีส่วนใหญ่ตัวเลือกที่เหมาะสมกว่าคือการจัดการกับมันและเขียนฟังก์ชั่นที่ทำการห่อด้วยตนเอง (ไม่ว่าจะเป็นการประยุกต์ใช้หรือโดยการเขียนบางส่วนของสเปคแบบโต้ตอบ
wasamasa

2
@wasamasa ฉันเห็นด้วยบางส่วน อย่างไรก็ตามมีหลายกรณีที่การใช้เครื่องมือวัดแบบอัตโนมัติเป็นข้อบังคับ edebugตัวอย่างคือ นอกจากนี้ยังมีฟังก์ชั่นที่ - การระบุinteractiveมีขนาดใหญ่กว่าร่างกายของฟังก์ชั่น ในกรณีเช่นนี้การเขียนinteractiveข้อกำหนดใหม่อาจค่อนข้างน่าเบื่อ คำถามและคำตอบตอบตามหลักการที่จำเป็น
Tobias

1
โดยส่วนตัวแล้วฉันพบว่าคำตอบนี้มีประโยชน์มากไม่เพียง แต่เกี่ยวกับขอบเขตของคำถามเท่านั้น แต่ยังแสดงให้เห็นถึงการใช้แมโครอย่างเป็นธรรมชาติและวิธีการหนึ่งขั้นตอนในการเริ่มต้นกับแมโคร ขอบคุณ!
gsl

11

ฉันต้องแก้ปัญหาที่คล้ายกันมากnadvice.elดังนั้นนี่คือวิธีแก้ปัญหา (ซึ่งใช้รหัสบางส่วนจาก nadvice.el):

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

เมื่อเปรียบเทียบกับโซลูชันอื่น ๆ ที่โพสต์ไปแล้วสิ่งนี้มีข้อได้เปรียบในการทำงานอย่างถูกต้องหากwrappeeได้รับการนิยามใหม่ด้วยสเปคแบบอินเทอร์แอคทีฟที่แตกต่างกัน

แน่นอนถ้าคุณต้องการให้เสื้อคลุมของคุณโปร่งใสอย่างแท้จริงคุณสามารถทำได้ง่ายขึ้น:

(defalias 'wrapper #'wrappee)

นี่เป็นคำตอบเดียวที่อนุญาตให้กำหนด wrapper ที่ค้นหา wraps ที่ runtime ตัวอย่างเช่นฉันต้องการเพิ่มทางลัดที่ดำเนินการที่กำหนดโดยคำสั่งบางอย่างที่ค้นหาที่รันไทม์ การใช้advice-eval-interactive-specตามที่แนะนำที่นี่ฉันสามารถสร้างข้อมูลจำเพาะเชิงโต้ตอบที่สอดคล้องกับ wrapper แบบไดนามิกนั้น
Igor Bukanov

มันเป็นไปได้ที่จะทำให้called-interactively-pกลับมาtในwrappee? มีfuncall-interactivelyแต่ไม่มีapply-interactively
clemera

1
@compunaut: แน่นอนคุณสามารถทำได้(apply #'funcall-interactively #'wrappee args)ถ้าคุณต้องการ แต่คุณควรจะทำก็ต่อเมื่อฟังก์ชั่นนั้นมีการโต้ตอบ(apply (if (called-interactively-p 'any) #'funcall-interactively #'funcall) #'wrappee args)กัน
สเตฟาน

ฮาขอบคุณ! อย่างใดไม่สามารถคิดนอกกรอบของฉัน
clemera

1

แก้ไข: คำตอบของโทเบียสนั้นดีกว่านี้เนื่องจากได้รับแบบโต้ตอบที่แม่นยำและ docstring ของฟังก์ชั่นที่พันแล้ว


เมื่อรวมคำตอบของ Aaron Harris และ kjo เข้าด้วยกันคุณอาจใช้สิ่งต่อไปนี้:

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

การใช้งาน:

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

Call wrapper ด้วย:

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

M-x wrapper-func

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.