Một chức năng hoặc macro có thể chỉ định cảnh báo trình biên dịch byte?


15

Tôi đang viết một hàm, về nguyên tắc, có một số lượng các đối số tùy ý. Trên thực tế, tuy nhiên, nó chỉ nên được thông qua một thậm chí số lượng các đối số, và sẽ tạo ra kết quả không mong muốn khác.

Đây là một ví dụ giả cho bối cảnh:

(defun my-caller (&rest args)
  (while args
    (call-other-function (pop args) (pop args))))

Khi một tệp elisp được biên dịch byte, trình biên dịch byte sẽ đưa ra cảnh báo khi thấy một hàm được gọi với số lượng đối số sai. Rõ ràng, điều đó sẽ không bao giờ xảy ra my-caller, vì nó được xác định là lấy bất kỳ số nào.

Tuy nhiên, có thể có một thuộc tính ký hiệu mà tôi có thể đặt hoặc một (declare)dạng tôi có thể thêm vào định nghĩa của nó. Một cái gì đó để thông báo cho người dùng rằng chức năng này chỉ nên được cung cấp một số lượng đối số chẵn.

  1. Có cách nào để thông báo cho trình biên dịch byte về hạn chế này không?
  2. Nếu không, có thể với một macro, thay vì một hàm?

"... Khi thấy một hàm được gọi với số lượng đối số sai "?
itjeyd

Câu trả lời:


13

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 funcallhoặc applytrong 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 funcallapplybâ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ó .elctệ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--functiontrự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-callerlà không còn là chức năng hạng nhất: bạn không thể chuyển nó đến funcallhoặc applytrong 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 applysẽ 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.


2
Biên dịch cảnh báo được tạo bằngbyte-compile-warn
Jordon Biondo

Bây giờ tôi tự hỏi nếu điều này có thể được thực hiện hiệu quả hơn bằng cách xác định một macro trình biên dịch cho hàm. Điều này sẽ loại bỏ nhược điểm của việc không applyhoặc funcalltrình bao bọc macro. Tôi sẽ thử nó và chỉnh sửa câu trả lời của tôi nếu nó hoạt động.
Jon O.

11

Có, bạn có thể sử dụng byte-defop-compilerđể thực sự chỉ định một hàm biên dịch hàm của mình, byte-defop-compilercó một số tính năng được tích hợp sẵn để giúp bạn xác định rằng các hàm của bạn sẽ đưa ra các cảnh báo dựa trên việc có một số đối số.

Tài liệu

Thêm một dạng trình biên dịch cho FUNCTION. Nếu chức năng là một ký hiệu, thì biến "byte-SYMBOL" phải đặt tên cho opcode sẽ được sử dụng. Nếu hàm là một danh sách, phần tử đầu tiên là hàm và phần tử thứ hai là ký hiệu mã byte. Phần tử thứ hai có thể là con số không, nghĩa là không có opcode. COMPILE-HANDLER là chức năng sử dụng để biên dịch byte-op này hoặc có thể là các chữ viết tắt 0, 1, 2, 3, 0-1 hoặc 1-2. Nếu không, thì trình xử lý là "byte-compile-SYMBOL.


Sử dụng

Trong trường hợp cụ thể của bạn, bạn có thể sử dụng một trong những từ viết tắt để xác định rằng hàm của bạn sẽ được cung cấp hai đối số.

(byte-defop-compiler my-caller 2)

Bây giờ chức năng của bạn sẽ đưa ra cảnh báo khi được biên dịch với bất cứ thứ gì trừ 2 args.

Nếu bạn muốn đưa ra cảnh báo cụ thể hơn và viết các hàm biên dịch của riêng bạn. Nhìn vào byte-compile-one-argvà các chức năng tương tự khác trong bytecomp.el để tham khảo.

Lưu ý rằng bạn không chỉ chỉ định một số chức năng để xử lý xác nhận, mà thực sự là quá trình biên dịch. Một lần nữa biên dịch các hàm trong bytecomp.el sẽ cung cấp cho bạn một tài liệu tham khảo tốt.


Tuyến an toàn hơn

Đây không phải là thứ tôi đã thấy được ghi lại hoặc thảo luận trực tuyến nhưng nhìn chung tôi sẽ nói rằng đây là một lộ trình không nên làm. Lộ trình chính xác (IMO) sẽ là viết các defun của bạn bằng chữ ký mô tả hoặc sử dụng một macro sẽ cung cấp các cảnh báo tại thời điểm mở rộng, kiểm tra độ dài của các đối số của bạn và sử dụng byte-compile-warnhoặc errorhiển thị lỗi. Nó cũng có thể có lợi cho bạn để sử dụng eval-when-compileđể kiểm tra lỗi.

Bạn cũng sẽ cần xác định chức năng của mình trước khi nó được sử dụng và lệnh gọi byte-defop-compilersẽ cần phải được thực hiện trước khi trình biên dịch nhận được các cuộc gọi thực tế của chức năng của bạn.

Một lần nữa, dường như không thực sự được ghi lại hoặc tư vấn từ những gì tôi đã thấy (có thể sai) nhưng tôi tưởng tượng mô hình cần tuân theo ở đây sẽ là chỉ định một số loại tệp tiêu đề cho gói của bạn chứa đầy một loạt các defun trống và gọi đến byte-defop-compiler. Đây về cơ bản sẽ là một gói được yêu cầu trước khi gói thực sự của bạn có thể được biên dịch.

Ý kiến: Dựa trên những gì tôi biết, không nhiều, vì tôi mới tìm hiểu về tất cả những điều này, tôi sẽ khuyên bạn đừng bao giờ làm bất cứ điều gì trong số này. không bao giờ


1
Liên quan: Có đơn giản hóa đơn giản bằng cách dạy các cảnh báo bổ sung cho trình biên dịch byte.
Wilfred Hughes
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.