EDIT : Một cách tốt hơn để làm điều này trong các Emac gần đây là xác định một macro trình biên dịch để kiểm tra số lượng đối số. Câu trả lời ban đầu của tôi bằng cách sử dụng một macro bình thường được bảo tồn bên dưới, nhưng một trình biên dịch-macro thì tốt hơn bởi vì nó không ngăn việc truyền hàm đến funcall
hoặc apply
trong thời gian chạy.
Trong các phiên bản gần đây của Emacs, bạn có thể thực hiện việc này bằng cách xác định macro trình biên dịch cho hàm của mình để kiểm tra số lượng đối số và đưa ra cảnh báo (hoặc thậm chí là lỗi) nếu nó không khớp. Sự tinh tế duy nhất là macro trình biên dịch sẽ trả về dạng cuộc gọi hàm ban đầu không thay đổi để đánh giá hoặc biên dịch. Điều này được thực hiện bằng cách sử dụng một &whole
đối số và trả về giá trị của nó. Điều này có thể được thực hiện như thế này:
(require 'cl-lib)
(defun my-caller (&rest args)
(while args
(message "%S %S" (pop args) (pop args))))
(define-compiler-macro my-caller (&whole form &rest args)
(when (not (cl-evenp (length args)))
(byte-compile-warn "`my-caller' requires an even number of arguments"))
form)
(my-caller 1 2 3 4)
(my-caller 1 2)
(funcall #'my-caller 1 2 3 4) ; ok
(apply #'my-caller '(1 2)) ; also ok
(my-caller 1) ; produces a warning
(funcall #'my-caller 1 2 3) ; no warning!
(apply #'my-caller '(1 2 3)) ; also no warning
Lưu ý rằng funcall
và apply
bây giờ có thể được sử dụng, nhưng chúng bỏ qua việc kiểm tra đối số bằng macro trình biên dịch. Mặc dù tên của họ, macro trình biên dịch cũng dường như được mở rộng trong quá trình 'giải thích' đánh giá qua C-xC-e, M-xeval-buffer, vì vậy bạn sẽ nhận được lỗi trên đánh giá cũng như về soạn thảo ví dụ này.
Câu trả lời gốc sau:
Đây là cách bạn có thể thực hiện đề xuất của Jordon để "sử dụng macro sẽ cung cấp các cảnh báo khi mở rộng". Hóa ra là rất dễ dàng:
(require 'cl-lib)
(defmacro my-caller (&rest args)
(if (cl-evenp (length args))
`(my-caller--function ,@args)
(error "Function `my-caller' requires an even number of arguments")))
(defun my-caller--function (&rest args)
;; function body goes here
args)
(my-caller 1 2 3 4)
(my-caller 1 2 3)
Cố gắng biên dịch ở trên trong một tệp sẽ thất bại (không có .elc
tệp nào được tạo ra), với thông báo lỗi có thể nhấp tốt trong nhật ký biên dịch, nêu rõ:
test.el:14:1:Error: `my-caller' requires an even number of arguments
Bạn cũng có thể thay thế (error …)
bằng (byte-compile-warn …)
để đưa ra cảnh báo thay vì lỗi, cho phép tiếp tục biên dịch. (Cảm ơn Jordon vì đã chỉ ra điều này trong các bình luận).
Vì các macro được mở rộng tại thời gian biên dịch, không có hình phạt thời gian chạy liên quan đến kiểm tra này. Tất nhiên, bạn không thể ngăn người khác gọi my-caller--function
trực tiếp, nhưng ít nhất bạn có thể quảng cáo nó dưới dạng chức năng "riêng tư" bằng cách sử dụng quy ước gạch nối kép.
Một nhược điểm đáng chú ý của việc sử dụng macro cho mục đích này my-caller
là không còn là chức năng hạng nhất: bạn không thể chuyển nó đến funcall
hoặc apply
trong thời gian chạy (hoặc ít nhất là nó sẽ không làm những gì bạn mong đợi). Về mặt đó, giải pháp này không tốt bằng việc có thể chỉ cần khai báo một cảnh báo trình biên dịch cho một hàm thực. Tất nhiên, việc sử dụng apply
sẽ khiến cho không thể kiểm tra số lượng đối số được truyền cho hàm vào thời gian biên dịch, vì vậy có lẽ đây là một sự đánh đổi chấp nhận được.