Функция defun внутри let с лексической привязкой выдает предупреждение при байтовой компиляции «функция не известна как определенная»

13

Я хочу получить эффект статической переменной, используя defunвнутри letс лексической привязкой для создания замыкания. Однако при байтовой компиляции файла я получаю предупреждение. Я делаю что-то не так, или если нет, есть ли способ подавить это предупреждение?

Я создал MCVE:

;; -*- lexical-binding: t -*-

(let ((count 0))
  (defun increase-count ()
    (interactive)
    (setq count (1+ count))
    (message "Count is: %d" count))

  ;; The warning happens here.
  (increase-count))

Код работает должным образом: функция increase-countвыводит «Count is: n», где n увеличивается при каждом вызове. Однако при байтовой компиляции этого файла я получаю следующее предупреждение:

In end of data:
mcve.el:11:1:Warning: the function ‘increase-count’ is not known to be
    defined.

Мне кажется, что increase-countвсегда следует определять перед вызовом в конце блока let. Разве это не так?

Уилл Кункель
источник
defunне делает то, что, как вы думаете, он делает, он всегда создает определение верхнего уровня. Элисп, в конце концов, не Схема ...
Васамаса
2
Я знаю, что это создает определение верхнего уровня; это то, что я хочу. Я просто хочу, чтобы это определение верхнего уровня было закрытием. Кажется, он работает так, как я хочу, за исключением этого предупреждения о компиляции байтов.
Уилл Кункель

Ответы:

7

Способ байтового компилятора решить, будет ли определена функция или нет, очень «наивен» и обманывается даже в вашем «очевидном» случае. Но вы можете написать это так, чтобы компилятор понимал, что происходит:

(defalias 'increase-count
  (let ((count 0))
    (lambda ()
      (interactive)
      (setq count (1+ count))
      (message "Count is: %d" count))))

Конечно, еще лучше было бы улучшить логику байтового компилятора: патчи приветствуются для этого.

Стефан
источник
5

Чтобы подавить предупреждение байт-компилятора, попробуйте добавить это перед вашим кодом, начиная со столбца 0 (крайний левый):

(declare-function increase-count "your-file-name.el")

C-h f declare-function говорит тебе:

declare-functionмакрос Lisp в subr.el.

(declare-function FN FILE &optional ARGLIST FILEONLY)

Сообщите байтовому компилятору, что функция FNопределена в FILE. FILEАргумент не используется байт-компилятором, но в check-declareпакете, который проверяет , что файл содержит определение FN.

FILEэто может быть либо файл Lisp (в этом случае ".el" расширение является необязательным), либо файл C. Файлы C расширены относительно "src/"каталога Emacs . Файлы Lisp ищутся для использования locate-library, и в случае неудачи они расширяются относительно местоположения файла, содержащего объявление. А FILEс "ext:"префиксом это внешний файл. check-declareпроверит такие файлы, если они найдены, и пропустит их без ошибок, если они не найдены.

Необязательно ARGLISTуказывает FNаргументы или tне указывает FNаргументы. Пропущено по ARGLISTумолчанию t, not nil: a nil ARGLISTуказывает пустой список аргументов, а явное t ARGLIST- это заполнитель, который позволяет предоставить более поздний аргумент.

Необязательный FILEONLYnon- nilозначает, что check-declareбудет проверяться только то FILE, что существует, а не то, что он определяет FN. Это предназначено для определения функций , которые check-declareне распознают, например, defstruct.

Обратите внимание, что в целях check-declare, этот оператор должен быть первым непробельным символом в строке.

Для получения дополнительной информации см. Инфо-узел (elisp)Declaring Functions.

Нарисовалась
источник
Нужен ли не ноль FILEONLYаргумент для рассматриваемого случая? Кстати, я бы дал тот же ответ ;-).
Тобиас
@Tobias: FILEONLYмне здесь не нужно. Что, казалось бы, указывает на то, что check-declareраспознает fи gопределяет.
Дрю
@Drew, я думаю, что последний комментарий о fи gимеет смысл только в контексте emacs.stackexchange.com/q/39439 ?
phils
@phils: Да, я хотел сказать это: FILEONLYмне здесь это не нужно. Что, казалось бы, указывает на то, что check-declareпризнает increase-countdefun. ;-)
Дрю
3

Я считаю , размещая определение в вопросе внутри eval-and-compileтакже поверхностно достичь того же результата , как и в Стефана правильный ответ :

(eval-and-compile
  (let ((count 0))
    (defun increase-count ()
      (interactive)
      (setq count (1+ count))
      (message "Count is: %d" count))))

Я, однако, едва знаком с тонкостями использования eval-and-compileи, кроме того, не ожидаю, что этот подход каким-либо образом превосходит.

Бэзил
источник