Чи може функція або макрос вказати попередження компілятора байт?


15

Я пишу функцію, яка, в принципі, бере довільну кількість аргументів. На практиці, однак, вона повинна коли-небудь передавати парну кількість аргументів, і це призведе до небажаних результатів в іншому випадку.

Ось фіктивний приклад для контексту:

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

Коли файл elisp компілюється в байті, компілятор байт видає попередження, коли бачить, що функція викликається з неправильною кількістю аргументів. Очевидно, що цього ніколи не відбудеться my-caller, оскільки визначено, щоб прийняти будь-яке число.

Але, можливо, є властивість символу, яку я можу встановити, або (declare)форма, яку я можу додати до її визначення. Щось, щоб сповістити користувача про те, що цій функції слід надати лише парну кількість аргументів.

  1. Чи є спосіб повідомити байт-компілятор про це обмеження?
  2. Якщо ні, чи можливо з макросом замість функції?

"... коли він бачить, що функція викликається з неправильною кількістю аргументів"?
itsjeyd

Відповіді:


13

EDIT : Кращий спосіб зробити це в останніх Emacs - це визначити макрос компілятора для перевірки кількості аргументів. Моя оригінальна відповідь із використанням звичайного макросу збережена нижче, але компілятор-макрос є вищим, оскільки це не заважає передавати функцію до funcallабо applyпід час виконання.

В останніх версіях Emacs це можна зробити, визначивши макрос компілятора для вашої функції, який перевіряє кількість аргументів і видає попередження (або навіть помилку), якщо воно не відповідає. Єдина тонкоща полягає в тому, що макрос компілятора повинен повертати початкову форму виклику функції без змін для оцінки чи компіляції. Це робиться за допомогою &wholeаргументу та повернення його значення. Це можна зробити так:

(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

Зауважте, що funcallі applyзараз їх можна використовувати, але вони обходять перевірку аргументів макросом компілятора. Незважаючи на свою назву, компілятор макроси також , здається , буде розширено в ході «витлумачено» оцінки через C-xC-e, M-xeval-bufferтак ви отримаєте помилки в оцінці, а також зі складання цього прикладу.


Оригінальна відповідь випливає:

Ось як можна реалізувати пропозицію Джордона "використовувати макрос, який надаватиме попередження під час розширення". Це виявляється дуже просто:

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

Спроба скласти вищезазначене у файл не вдасться (жоден .elcфайл не виробляється), із приємним повідомленням про помилку, що можна натиснути в журналі компіляції, зазначаючи:

test.el:14:1:Error: `my-caller' requires an even number of arguments

Крім того, можна замінити (error …)з , (byte-compile-warn …)щоб зробити попередження замість помилки, дозволяючи продовжити компіляцію. (Дякую Джордону за те, що він це вказав у коментарях).

Оскільки макроси розгортаються під час компіляції, не передбачено покарання за час виконання, пов’язаного з цією перевіркою. Звичайно, ви не можете зупинити інших людей, які дзвонять my-caller--functionбезпосередньо, але ви можете, принаймні, рекламувати це як "приватну" функцію, використовуючи подвійний дефіс.

Помітним недоліком використання макросу для цієї мети є те, що my-callerце вже не функція першого класу: ви не можете передавати його до funcallабо applyпід час виконання (або, принаймні, він не буде робити те, що ви очікуєте). У цьому відношенні це рішення не так добре, як можливість просто оголосити попередження компілятора для реальної функції. Звичайно, використання не applyдозволило б перевірити кількість аргументів, що передаються функції в час компіляції, тому, можливо, це є прийнятним компромісом.


2
Попередження щодо компіляції створюються зbyte-compile-warn
Джордона Біондо

Мені зараз цікаво, чи можна це зробити ефективніше, визначивши макрос компілятора для функції. Це призведе до усунення недоліків не бути макрос applyабо funcallобгорткою. Я спробую це і відредагую свою відповідь, якщо вона працює.
Джон О.

11

Так, ви можете за допомогою byte-defop-compilerфактично вказати функцію, яка компілює вашу функцію, byte-defop-compilerмає деякі вбудовані смаки, які допоможуть вам визначити, що ваші функції повинні отримувати попередження на основі наявності декількох аргументів.

Документація

Додайте форму компілятора для функції FUNCTION. Якщо функція є символом, то змінна "байт-SYMBOL" повинна називати опкод, який буде використовуватися. Якщо функцією є список, перший елемент є функцією, а другий елемент - символом байт-коду. Другий елемент може бути нульовим, тобто немає опкоду. COMPILE-HANDLER - це функція, яка використовується для компіляції цього байтового режиму, або може бути абревіатурами 0, 1, 2, 3, 0-1 або 1-2. Якщо він дорівнює нулю, обробник - це "байт-компіляція-SYMBOL".


Використання

У вашому конкретному випадку ви можете використовувати одну із скорочень, щоб визначити, що вашій функції слід надати два аргументи.

(byte-defop-compiler my-caller 2)

Тепер ви функціонуватимуть попередження, коли компілюються що-небудь, крім двох аргументів.

Якщо ви хочете дати більш конкретні застереження і написати свої функції компілятора. Подивіться byte-compile-one-argта інші подібні функції в bytecomp.el для довідки.

Зауважте, що ви не просто вказуєте якусь функцію для обробки валідації, але насправді і компіляцію. Знову компілюйте функції в bytecomp.el забезпечить вам хороший довідник.


Більш безпечні маршрути

Це не те, що я бачив документально або обговорював в Інтернеті, але в цілому, я б сказав, що це маршрут необдумано пройти. Правильним маршрутом (IMO) було б записувати свої виправлення з описовими підписами або використовувати макрос, який надаватиме попередження під час розширення, перевіряючи довжину ваших аргументів та використовуючи byte-compile-warnабо errorпоказуючи помилки. Можливо, це також допоможе вам скористатися, eval-when-compileщоб зробити перевірку помилок.

Вам також буде потрібно, щоб ваша функція була визначена до того, як вона буде використана, і заклик до byte-defop-compilerцього повинен бути, перш ніж компілятор потрапить на фактичні виклики вашої функції.

Знову ж таки, схоже, насправді не задокументовано чи не радиться з того, що я бачив (могло бути помилковим), але я думаю, що модель, яку слід дотримуватися тут, полягала б у тому, щоб вказати якийсь файл заголовка для вашого пакету, який сповнений купою порожніх розрядів і дзвінки до byte-defop-compiler. Це в основному пакет, необхідний для того, щоб ваш реальний пакет був змонтований.

Думка: Виходячи з того, що я знаю, а це не так багато, тому що я щойно дізнався про все це, я б порадив вам ніколи цього не робити. коли-небудь


1
Пов'язане: Існує bytecomp-спростити, який вчить компілятору байт додатковим попередженням.
Вілфред Х'юз
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.