Làm thế nào để viết một trình bao bọc chức năng thông qua trong suốt của Viking?


10

Ý của tôi là "trình bao bọc hàm" trong suốt "là một hàm, hãy gọi nó wrapper, trả về kết quả từ việc chuyển tất cả đối số của nó sang một số hàm khác, hãy gọi nó wrappee.

Làm thế nào điều này được thực hiện trong Emacs Lisp?

NB: Hàm lý tưởng wrapperbất khả tri về wrappeechữ ký của hàm; tức là nó không biết gì về số lượng, vị trí, tên, v.v. của wrappeecác đối số; nó chỉ chuyển tất cả các đối số của nó theo wrappee, giống như là wrappeelần đầu tiên được gọi. (Tuy nhiên, không cần phải làm phiền với ngăn xếp cuộc gọi để thay thế cuộc gọi wrapperbằng cuộc gọi đến wrappee.)

Tôi đăng một phần câu trả lời cho câu hỏi của tôi:

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

Điều này chỉ hoạt động khi wrappeekhông tương tác. Rõ ràng, cách các chức năng tương tác có được các đối số của chúng đại diện cho một "kênh" khác với những gì được bao phủ bởi (&rest args)câu thần chú. Những gì tôi vẫn cần, do đó, là một equally- wrappeeđối -agnostic của (&rest args)chữ ký cho trường hợp wrappeelà một tương tác chức năng.

(Câu hỏi này được thúc đẩy bởi một vấn đề được mô tả trong câu hỏi trước đó .)


Trong trường hợp cần làm rõ thêm những gì tôi yêu cầu, dưới đây là một vài ví dụ, hiển thị tương đương Python và JavaScript của những gì tôi đang theo dõi.

Trong Python, một vài cách tiêu chuẩn để thực hiện trình bao bọc như vậy được hiển thị bên dưới:

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

# or

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

(Ở đây *argslà viết tắt của "tất cả các đối số vị trí" và **kwargslà viết tắt của "tất cả các đối số từ khóa".)

Tương đương JavaScript sẽ giống như thế này:

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

// or

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

Đối với bản ghi, tôi không đồng ý rằng câu hỏi này là một bản sao của Cách áp dụng mapcar cho một hàm có nhiều đối số . Tôi không thể giải thích được tại sao, vì hai câu hỏi trông rất khác nhau đối với tôi. Nó giống như được hỏi "giải thích tại sao một quả táo không nên được coi là tương đương với một quả cam". Câu hỏi đơn thuần là rất điên rồ, đến nỗi người ta nghi ngờ người ta có thể đưa ra một câu trả lời sẽ thỏa mãn người hỏi nó.


Bạn đã xem xét sử dụng lời khuyên / nadvice?
wasamasa

@wasamasa: không, và hơn nữa, tôi không biết lời khuyên / nadvice sẽ áp dụng cho câu hỏi này như thế nào. Trong mọi trường hợp, tôi thấy các advicecông cụ đủ vấn đề mà tôi muốn tránh xa nó. Trên thực tế, động lực cho câu hỏi này là cố gắng tìm giải pháp cho một vấn đề khó hiểu khác mà tôi có với chức năng được
khuyến khích

1
@wasamasa: Lời khuyên trình bày vấn đề tương tự. Bạn có thể nói cho nó biết phải làm gì với bất kỳ đối số nào, nhưng để làm cho nó tương tác, bạn cần xác định cách cung cấp các đối số. IOW, bạn cần cung cấp một interactivethông số kỹ thuật.
vẽ

1
Điều tôi muốn nói là khuyên chức năng tương tác ban đầu làm bất cứ điều gì bạn muốn xảy ra trước và sau nó, theo cách đó bạn không cần phải lo lắng về thông số tương tác.
wasamasa

2
@wasamasa: Vâng, nhưng đó là khác nhau. Lời khuyên luôn dành cho một chức năng cụ thể , cho dù có tương tác hay không. Và nếu đó là một lệnh thì không có vấn đề gì - hành vi tương tác của nó được kế thừa cho lệnh được khuyên (trừ khi lời khuyên xác định lại hành vi tương tác). Câu hỏi này là về một chức năng / lệnh tùy ý , không phải là một chức năng cụ thể.
vẽ

Câu trả lời:


11

Tất nhiên nó có thể bao gồm các interactiveđặc điểm kỹ thuật. Chúng tôi đang đối phó ở đây với elisp ! (Lisp là ngôn ngữ mà các cấu trúc quan trọng nhất là danh sách. Các hình thức có thể gọi được chỉ là danh sách. Vì vậy, bạn có thể xây dựng chúng theo ý thích của mình.)

Ứng dụng: Bạn muốn thêm một số chức năng cho một số chức năng tự động. Các chức năng mở rộng nên được đặt tên mới để defadvicekhông áp dụng.

Đầu tiên một phiên bản phù hợp khá chính xác mục đích của bạn. Chúng tôi đặt ô chức năng ( fset) của biểu tượng wrappervới tất cả thông tin bắt buộc từ wrappeevà thêm nội dung bổ sung của chúng tôi.

Nó hoạt động cho cả hai wrappeeđịnh nghĩa. Phiên bản đầu tiên wrappeelà tương tác thứ hai thì không.

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

Nhưng sẽ thuận tiện hơn khi định nghĩa một macro xây dựng các hàm mở rộng. Do đó, chúng ta thậm chí có thể chỉ định các tên hàm sau đó. (Tốt cho phiên bản tự động.)

Sau khi thực thi mã bên dưới, bạn có thể gọi wrapper-interactivetương tác và wrapper-non-interactivekhông tương tác.

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

Lưu ý, đến nay tôi vẫn chưa tìm được cách chuyển biểu mẫu khai báo nhưng điều đó cũng có thể xảy ra.


2
Hừm, có người bình chọn câu trả lời này. Tôi không thực sự quan tâm đến điểm số nhưng điều tôi quan tâm là lý do bỏ phiếu trả lời. Nếu bạn bỏ phiếu, xin vui lòng để lại nhận xét! Điều này sẽ cho tôi một cơ hội để cải thiện câu trả lời.
Tobias

Tôi không biết chắc chắn, nhưng điều này chắc chắn sẽ khiến bất cứ ai đọc mã của gói sử dụng nó đi WTF. Trong hầu hết các trường hợp, tùy chọn hợp lý hơn là xử lý nó và viết một hàm thực hiện gói thủ công (có thể áp dụng hoặc bằng cách viết lại các phần của thông số tương tác.
wasamasa

2
@wasamasa Mình đồng ý một phần. Tuy nhiên, có những trường hợp thiết bị tự động là bắt buộc. Một ví dụ là edebug. Hơn nữa, có những chức năng trong đó đặc tả interactivelớn hơn đáng kể so với thân hàm. Trong những trường hợp như vậy, việc viết lại interactiveđặc tả có thể khá tẻ nhạt. Câu hỏi và câu trả lời giải quyết các nguyên tắc cần thiết.
Tobias

1
Cá nhân, tôi thấy câu trả lời này khá mang tính hướng dẫn, không chỉ liên quan đến phạm vi câu hỏi, mà còn cho thấy một ứng dụng tự nhiên của các macro và cách một bước chuyển từ macro sang macro. Cảm ơn!
gsl

11

Tôi đã phải giải quyết một vấn đề rất giống nhau nadvice.el, vì vậy đây là một giải pháp (sử dụng một số mã từ nadvice.el):

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

So với các giải pháp khác được đăng cho đến nay, giải pháp này có lợi thế là hoạt động chính xác nếu wrappeeđược xác định lại bằng một thông số tương tác khác (nghĩa là nó sẽ không tiếp tục sử dụng thông số cũ).

Tất nhiên, nếu bạn muốn trình bao bọc của bạn thực sự trong suốt, bạn có thể làm điều đó đơn giản hơn:

(defalias 'wrapper #'wrappee)

Đây là câu trả lời duy nhất cho phép xác định trình bao bọc tìm thấy những gì nó kết thúc khi chạy. Ví dụ, tôi muốn thêm một lối tắt thực hiện một hành động được xác định bởi một số lệnh được tra cứu trong thời gian chạy. Sử dụng advice-eval-interactive-specnhư được đề xuất ở đây tôi có thể xây dựng thông số tương tác tương ứng với trình bao bọc động đó.
Igor Bukanov

Có thể thực hiện called-interactively-ptrở lại ttrong wrappee? Có funcall-interactivelynhưng khôngapply-interactively
clemera

1
@compunaut: Tất nhiên, bạn có thể làm (apply #'funcall-interactively #'wrappee args)nếu muốn. Nhưng bạn chỉ nên làm điều đó nếu chức năng được gọi là tương tác, vì vậy một cái gì đó như thế (apply (if (called-interactively-p 'any) #'funcall-interactively #'funcall) #'wrappee args).
Stefan

Hà, cảm ơn! Bằng cách nào đó không thể nghĩ ra bên ngoài hộp của tôi.
clemera

1

chỉnh sửa: Câu trả lời của Tobias đẹp hơn câu này, vì nó có được dạng tương tác chính xác và chuỗi tài liệu của hàm được gói.


Kết hợp câu trả lời của Aaron Harris và kjo, bạn có thể sử dụng một cái gì đó như:

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

Sử dụng:

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

Gọi trình bao bọc với một trong hai:

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

M-x wrapper-func

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.